all repos — honk @ 7ace81cff1db29a6a887a6f43443157aa74cec64

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 !canedithonk(user, honk) {
 906		http.Error(w, "no editing that please", http.StatusInternalServerError)
 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
 933func canedithonk(user *WhatAbout, honk *Honk) bool {
 934	if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
 935		return false
 936	}
 937	return true
 938}
 939
 940// what a hot mess this function is
 941func submithonk(w http.ResponseWriter, r *http.Request) {
 942	rid := r.FormValue("rid")
 943	noise := r.FormValue("noise")
 944
 945	userinfo := login.GetUserInfo(r)
 946	user, _ := butwhatabout(userinfo.Username)
 947
 948	dt := time.Now().UTC()
 949	updatexid := r.FormValue("updatexid")
 950	var honk *Honk
 951	if updatexid != "" {
 952		honk = getxonk(userinfo.UserID, updatexid)
 953		if !canedithonk(user, honk) {
 954			http.Error(w, "no editing that please", http.StatusInternalServerError)
 955			return
 956		}
 957		honk.Date = dt
 958		honk.What = "update"
 959		honk.Format = "markdown"
 960	} else {
 961		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
 962		what := "honk"
 963		if rid != "" {
 964			what = "tonk"
 965		}
 966		honk = &Honk{
 967			UserID:   userinfo.UserID,
 968			Username: userinfo.Username,
 969			What:     what,
 970			Honker:   user.URL,
 971			XID:      xid,
 972			Date:     dt,
 973			Format:   "markdown",
 974		}
 975	}
 976
 977	noise = hooterize(noise)
 978	honk.Noise = noise
 979	translate(honk)
 980
 981	var convoy string
 982	if rid != "" {
 983		xonk := getxonk(userinfo.UserID, rid)
 984		if xonk != nil {
 985			if xonk.Public {
 986				honk.Audience = append(honk.Audience, xonk.Audience...)
 987			}
 988			convoy = xonk.Convoy
 989		} else {
 990			xonkaud, c := whosthere(rid)
 991			honk.Audience = append(honk.Audience, xonkaud...)
 992			convoy = c
 993		}
 994		for i, a := range honk.Audience {
 995			if a == thewholeworld {
 996				honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
 997				break
 998			}
 999		}
1000		honk.RID = rid
1001	} else {
1002		honk.Audience = []string{thewholeworld}
1003	}
1004	if honk.Noise != "" && honk.Noise[0] == '@' {
1005		honk.Audience = append(grapevine(honk.Noise), honk.Audience...)
1006	} else {
1007		honk.Audience = append(honk.Audience, grapevine(honk.Noise)...)
1008	}
1009
1010	if convoy == "" {
1011		convoy = "data:,electrichonkytonk-" + xfiltrate()
1012	}
1013	butnottooloud(honk.Audience)
1014	honk.Audience = oneofakind(honk.Audience)
1015	if len(honk.Audience) == 0 {
1016		log.Printf("honk to nowhere")
1017		http.Error(w, "honk to nowhere...", http.StatusNotFound)
1018		return
1019	}
1020	honk.Public = !keepitquiet(honk.Audience)
1021	honk.Convoy = convoy
1022
1023	donkxid := r.FormValue("donkxid")
1024	if donkxid == "" {
1025		file, filehdr, err := r.FormFile("donk")
1026		if err == nil {
1027			var buf bytes.Buffer
1028			io.Copy(&buf, file)
1029			file.Close()
1030			data := buf.Bytes()
1031			xid := xfiltrate()
1032			var media, name string
1033			img, err := image.Vacuum(&buf, image.Params{MaxWidth: 2048, MaxHeight: 2048})
1034			if err == nil {
1035				data = img.Data
1036				format := img.Format
1037				media = "image/" + format
1038				if format == "jpeg" {
1039					format = "jpg"
1040				}
1041				name = xid + "." + format
1042				xid = name
1043			} else {
1044				maxsize := 100000
1045				if len(data) > maxsize {
1046					log.Printf("bad image: %s too much text: %d", err, len(data))
1047					http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1048					return
1049				}
1050				for i := 0; i < len(data); i++ {
1051					if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1052						log.Printf("bad image: %s not text: %d", err, data[i])
1053						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1054						return
1055					}
1056				}
1057				media = "text/plain"
1058				name = filehdr.Filename
1059				if name == "" {
1060					name = xid + ".txt"
1061				}
1062				xid += ".txt"
1063			}
1064			desc := r.FormValue("donkdesc")
1065			if desc == "" {
1066				desc = name
1067			}
1068			url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1069			fileid, err := savefile(xid, name, desc, url, media, true, data)
1070			if err != nil {
1071				log.Printf("unable to save image: %s", err)
1072				return
1073			}
1074			var d Donk
1075			d.FileID = fileid
1076			honk.Donks = append(honk.Donks, &d)
1077			donkxid = d.XID
1078		}
1079	} else {
1080		xid := donkxid
1081		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1082		donk := finddonk(url)
1083		if donk != nil {
1084			honk.Donks = append(honk.Donks, donk)
1085		} else {
1086			log.Printf("can't find file: %s", xid)
1087		}
1088	}
1089	herd := herdofemus(honk.Noise)
1090	for _, e := range herd {
1091		donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1092		if donk != nil {
1093			donk.Name = e.Name
1094			honk.Donks = append(honk.Donks, donk)
1095		}
1096	}
1097	memetize(honk)
1098
1099	placename := r.FormValue("placename")
1100	placelat := r.FormValue("placelat")
1101	placelong := r.FormValue("placelong")
1102	if placename != "" || placelat != "" || placelong != "" {
1103		p := new(Place)
1104		p.Name = placename
1105		p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1106		p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1107		p.Url = r.FormValue("placeurl")
1108		honk.Place = p
1109	}
1110	timestart := r.FormValue("timestart")
1111	if timestart != "" {
1112		t := new(Time)
1113		now := time.Now().Local()
1114		for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1115			start, err := time.ParseInLocation(layout, timestart, now.Location())
1116			if err == nil {
1117				if start.Year() == 0 {
1118					start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1119				}
1120				t.StartTime = start
1121				break
1122			}
1123		}
1124		timeend := r.FormValue("timeend")
1125		dur, err := time.ParseDuration(timeend)
1126		if err == nil {
1127			t.Duration = Duration(dur)
1128		}
1129		if !t.StartTime.IsZero() {
1130			honk.What = "event"
1131			honk.Time = t
1132		}
1133	}
1134
1135	if honk.Public {
1136		honk.Whofore = 2
1137	} else {
1138		honk.Whofore = 3
1139	}
1140
1141	// back to markdown
1142	honk.Noise = noise
1143
1144	if r.FormValue("preview") == "preview" {
1145		honks := []*Honk{honk}
1146		reverbolate(userinfo.UserID, honks)
1147		templinfo := getInfo(r)
1148		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1149		templinfo["Honks"] = honks
1150		templinfo["InReplyTo"] = r.FormValue("rid")
1151		templinfo["Noise"] = r.FormValue("noise")
1152		templinfo["SavedFile"] = donkxid
1153		templinfo["ServerMessage"] = "honk preview"
1154		err := readviews.Execute(w, "honkpage.html", templinfo)
1155		if err != nil {
1156			log.Print(err)
1157		}
1158		return
1159	}
1160
1161	if updatexid != "" {
1162		updatehonk(honk)
1163		oldjonks.Clear(honk.XID)
1164	} else {
1165		err := savehonk(honk)
1166		if err != nil {
1167			log.Printf("uh oh")
1168			return
1169		}
1170	}
1171
1172	// reload for consistency
1173	honk.Donks = nil
1174	donksforhonks([]*Honk{honk})
1175
1176	go honkworldwide(user, honk)
1177
1178	http.Redirect(w, r, honk.XID, http.StatusSeeOther)
1179}
1180
1181func showhonkers(w http.ResponseWriter, r *http.Request) {
1182	userinfo := login.GetUserInfo(r)
1183	templinfo := getInfo(r)
1184	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1185	templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1186	err := readviews.Execute(w, "honkers.html", templinfo)
1187	if err != nil {
1188		log.Print(err)
1189	}
1190}
1191
1192var combocache = cacheNew(cacheOptions{Filler: func(userid int64) ([]string, bool) {
1193	honkers := gethonkers(userid)
1194	var combos []string
1195	for _, h := range honkers {
1196		combos = append(combos, h.Combos...)
1197	}
1198	for i, c := range combos {
1199		if c == "-" {
1200			combos[i] = ""
1201		}
1202	}
1203	combos = oneofakind(combos)
1204	sort.Strings(combos)
1205	return combos, true
1206}})
1207
1208func showcombos(w http.ResponseWriter, r *http.Request) {
1209	userinfo := login.GetUserInfo(r)
1210	var combos []string
1211	combocache.Get(userinfo.UserID, &combos)
1212	templinfo := getInfo(r)
1213	err := readviews.Execute(w, "combos.html", templinfo)
1214	if err != nil {
1215		log.Print(err)
1216	}
1217}
1218
1219func submithonker(w http.ResponseWriter, r *http.Request) {
1220	u := login.GetUserInfo(r)
1221	name := r.FormValue("name")
1222	url := r.FormValue("url")
1223	peep := r.FormValue("peep")
1224	combos := r.FormValue("combos")
1225	combos = " " + strings.TrimSpace(combos) + " "
1226	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1227
1228	defer combocache.Clear(u.UserID)
1229
1230	if honkerid > 0 {
1231		goodbye := r.FormValue("goodbye")
1232		if goodbye == "F" {
1233			db := opendatabase()
1234			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1235				honkerid, u.UserID)
1236			err := row.Scan(&url)
1237			if err != nil {
1238				log.Printf("can't get honker xid: %s", err)
1239				return
1240			}
1241			log.Printf("unsubscribing from %s", url)
1242			user, _ := butwhatabout(u.Username)
1243			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, "sub")
1244			if err != nil {
1245				log.Printf("error updating honker: %s", err)
1246				return
1247			}
1248			go itakeitallback(user, url)
1249
1250			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1251			return
1252		}
1253		if goodbye == "X" {
1254			db := opendatabase()
1255			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1256				honkerid, u.UserID)
1257			err := row.Scan(&url)
1258			if err != nil {
1259				log.Printf("can't get honker xid: %s", err)
1260				return
1261			}
1262			log.Printf("resubscribing to %s", url)
1263			user, _ := butwhatabout(u.Username)
1264			_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "unsub")
1265			if err == nil {
1266				_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, "peep")
1267			}
1268			if err != nil {
1269				log.Printf("error updating honker: %s", err)
1270				return
1271			}
1272			go subsub(user, url)
1273
1274			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1275			return
1276		}
1277		_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1278		if err != nil {
1279			log.Printf("update honker err: %s", err)
1280			return
1281		}
1282		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1283		return
1284	}
1285
1286	flavor := "presub"
1287	if peep == "peep" {
1288		flavor = "peep"
1289	}
1290	p, err := investigate(url)
1291	if err != nil {
1292		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1293		log.Printf("failed to investigate honker: %s", err)
1294		return
1295	}
1296	url = p.XID
1297
1298	db := opendatabase()
1299	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1300	var x string
1301	err = row.Scan(&x)
1302	if err != sql.ErrNoRows {
1303		http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1304		if err != nil {
1305			log.Printf("honker scan err: %s", err)
1306		}
1307		return
1308	}
1309
1310	if name == "" {
1311		name = p.Handle
1312	}
1313	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos)
1314	if err != nil {
1315		log.Print(err)
1316		return
1317	}
1318	if flavor == "presub" {
1319		user, _ := butwhatabout(u.Username)
1320		go subsub(user, url)
1321	}
1322	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1323}
1324
1325func hfcspage(w http.ResponseWriter, r *http.Request) {
1326	userinfo := login.GetUserInfo(r)
1327
1328	filters := getfilters(userinfo.UserID, filtAny)
1329
1330	templinfo := getInfo(r)
1331	templinfo["Filters"] = filters
1332	templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1333	err := readviews.Execute(w, "hfcs.html", templinfo)
1334	if err != nil {
1335		log.Print(err)
1336	}
1337}
1338
1339func savehfcs(w http.ResponseWriter, r *http.Request) {
1340	userinfo := login.GetUserInfo(r)
1341	itsok := r.FormValue("itsok")
1342	if itsok == "iforgiveyou" {
1343		hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1344		_, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1345		if err != nil {
1346			log.Printf("error deleting filter: %s", err)
1347		}
1348		filtcache.Clear(userinfo.UserID)
1349		http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1350		return
1351	}
1352
1353	filt := new(Filter)
1354	filt.Name = r.FormValue("name")
1355	filt.Date = time.Now().UTC()
1356	filt.Actor = r.FormValue("actor")
1357	filt.IncludeAudience = r.FormValue("incaud") == "yes"
1358	filt.Text = r.FormValue("filttext")
1359	filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1360	filt.AnnounceOf = r.FormValue("announceof")
1361	filt.Reject = r.FormValue("doreject") == "yes"
1362	filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1363	filt.Hide = r.FormValue("dohide") == "yes"
1364	filt.Collapse = r.FormValue("docollapse") == "yes"
1365	filt.Rewrite = r.FormValue("filtrewrite")
1366	filt.Replace = r.FormValue("filtreplace")
1367
1368	if filt.Actor == "" && filt.Text == "" {
1369		log.Printf("blank filter")
1370		return
1371	}
1372
1373	j, err := jsonify(filt)
1374	if err == nil {
1375		_, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1376	}
1377	if err != nil {
1378		log.Printf("error saving filter: %s", err)
1379	}
1380
1381	filtcache.Clear(userinfo.UserID)
1382	http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1383}
1384
1385func accountpage(w http.ResponseWriter, r *http.Request) {
1386	u := login.GetUserInfo(r)
1387	user, _ := butwhatabout(u.Username)
1388	templinfo := getInfo(r)
1389	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1390	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1391	templinfo["User"] = user
1392	err := readviews.Execute(w, "account.html", templinfo)
1393	if err != nil {
1394		log.Print(err)
1395	}
1396}
1397
1398func dochpass(w http.ResponseWriter, r *http.Request) {
1399	err := login.ChangePassword(w, r)
1400	if err != nil {
1401		log.Printf("error changing password: %s", err)
1402	}
1403	http.Redirect(w, r, "/account", http.StatusSeeOther)
1404}
1405
1406func fingerlicker(w http.ResponseWriter, r *http.Request) {
1407	orig := r.FormValue("resource")
1408
1409	log.Printf("finger lick: %s", orig)
1410
1411	if strings.HasPrefix(orig, "acct:") {
1412		orig = orig[5:]
1413	}
1414
1415	name := orig
1416	idx := strings.LastIndexByte(name, '/')
1417	if idx != -1 {
1418		name = name[idx+1:]
1419		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1420			log.Printf("foreign request rejected")
1421			name = ""
1422		}
1423	} else {
1424		idx = strings.IndexByte(name, '@')
1425		if idx != -1 {
1426			name = name[:idx]
1427			if name+"@"+serverName != orig {
1428				log.Printf("foreign request rejected")
1429				name = ""
1430			}
1431		}
1432	}
1433	user, err := butwhatabout(name)
1434	if err != nil {
1435		http.NotFound(w, r)
1436		return
1437	}
1438	if stealthmode(user.ID, r) {
1439		http.NotFound(w, r)
1440		return
1441	}
1442
1443	j := junk.New()
1444	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1445	j["aliases"] = []string{user.URL}
1446	var links []junk.Junk
1447	l := junk.New()
1448	l["rel"] = "self"
1449	l["type"] = `application/activity+json`
1450	l["href"] = user.URL
1451	links = append(links, l)
1452	j["links"] = links
1453
1454	w.Header().Set("Cache-Control", "max-age=3600")
1455	w.Header().Set("Content-Type", "application/jrd+json")
1456	j.Write(w)
1457}
1458
1459func somedays() string {
1460	secs := 432000 + notrand.Int63n(432000)
1461	return fmt.Sprintf("%d", secs)
1462}
1463
1464func avatate(w http.ResponseWriter, r *http.Request) {
1465	n := r.FormValue("a")
1466	a := avatar(n)
1467	w.Header().Set("Cache-Control", "max-age="+somedays())
1468	w.Write(a)
1469}
1470
1471func servecss(w http.ResponseWriter, r *http.Request) {
1472	fd, err := os.Open("views" + r.URL.Path)
1473	if err != nil {
1474		http.NotFound(w, r)
1475		return
1476	}
1477	w.Header().Set("Cache-Control", "max-age=0")
1478	w.Header().Set("Content-Type", "text/css; charset=utf-8")
1479	err = css.Filter(fd, w)
1480	if err != nil {
1481		log.Printf("error filtering css: %s", err)
1482	}
1483}
1484func serveasset(w http.ResponseWriter, r *http.Request) {
1485	w.Header().Set("Cache-Control", "max-age=7776000")
1486	http.ServeFile(w, r, "views"+r.URL.Path)
1487}
1488func servehelp(w http.ResponseWriter, r *http.Request) {
1489	name := mux.Vars(r)["name"]
1490	w.Header().Set("Cache-Control", "max-age=600")
1491	http.ServeFile(w, r, "docs/"+name)
1492}
1493func servehtml(w http.ResponseWriter, r *http.Request) {
1494	templinfo := getInfo(r)
1495	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1496	if err != nil {
1497		log.Print(err)
1498	}
1499}
1500func serveemu(w http.ResponseWriter, r *http.Request) {
1501	xid := mux.Vars(r)["xid"]
1502	w.Header().Set("Cache-Control", "max-age="+somedays())
1503	http.ServeFile(w, r, "emus/"+xid)
1504}
1505func servememe(w http.ResponseWriter, r *http.Request) {
1506	xid := mux.Vars(r)["xid"]
1507	w.Header().Set("Cache-Control", "max-age="+somedays())
1508	http.ServeFile(w, r, "memes/"+xid)
1509}
1510
1511func servefile(w http.ResponseWriter, r *http.Request) {
1512	xid := mux.Vars(r)["xid"]
1513	row := stmtGetFileData.QueryRow(xid)
1514	var media string
1515	var data []byte
1516	err := row.Scan(&media, &data)
1517	if err != nil {
1518		log.Printf("error loading file: %s", err)
1519		http.NotFound(w, r)
1520		return
1521	}
1522	w.Header().Set("Content-Type", media)
1523	w.Header().Set("X-Content-Type-Options", "nosniff")
1524	w.Header().Set("Cache-Control", "max-age="+somedays())
1525	w.Write(data)
1526}
1527
1528func nomoroboto(w http.ResponseWriter, r *http.Request) {
1529	io.WriteString(w, "User-agent: *\n")
1530	io.WriteString(w, "Disallow: /a\n")
1531	io.WriteString(w, "Disallow: /d\n")
1532	io.WriteString(w, "Disallow: /meme\n")
1533	for _, u := range allusers() {
1534		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1535	}
1536}
1537
1538func webhydra(w http.ResponseWriter, r *http.Request) {
1539	u := login.GetUserInfo(r)
1540	userid := u.UserID
1541	templinfo := getInfo(r)
1542	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1543	page := r.FormValue("page")
1544	var honks []*Honk
1545	switch page {
1546	case "atme":
1547		honks = gethonksforme(userid)
1548		templinfo["ServerMessage"] = "at me!"
1549	case "home":
1550		honks = gethonksforuser(userid)
1551		honks = osmosis(honks, userid)
1552		templinfo["ServerMessage"] = serverMsg
1553	case "first":
1554		honks = gethonksforuserfirstclass(userid)
1555		honks = osmosis(honks, userid)
1556		templinfo["ServerMessage"] = "first class only"
1557	case "combo":
1558		c := r.FormValue("c")
1559		honks = gethonksbycombo(userid, c)
1560		honks = osmosis(honks, userid)
1561		templinfo["ServerMessage"] = "honks by combo: " + c
1562	case "convoy":
1563		c := r.FormValue("c")
1564		honks = gethonksbyconvoy(userid, c)
1565		templinfo["ServerMessage"] = "honks in convoy: " + c
1566	case "honker":
1567		xid := r.FormValue("xid")
1568		if strings.IndexByte(xid, '@') != -1 {
1569			xid = gofish(xid)
1570		}
1571		honks = gethonksbyxonker(userid, xid)
1572		xid = html.EscapeString(xid)
1573		msg := fmt.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
1574		templinfo["ServerMessage"] = template.HTML(msg)
1575	default:
1576		http.NotFound(w, r)
1577	}
1578	if len(honks) > 0 {
1579		templinfo["TopXID"] = honks[0].XID
1580	}
1581	if topxid := r.FormValue("topxid"); topxid != "" {
1582		for i, h := range honks {
1583			if h.XID == topxid {
1584				honks = honks[0:i]
1585				break
1586			}
1587		}
1588		log.Printf("topxid %d frags", len(honks))
1589	}
1590	reverbolate(userid, honks)
1591	templinfo["Honks"] = honks
1592	w.Header().Set("Content-Type", "text/html; charset=utf-8")
1593	err := readviews.Execute(w, "honkfrags.html", templinfo)
1594	if err != nil {
1595		log.Printf("frag error: %s", err)
1596	}
1597}
1598
1599func serve() {
1600	db := opendatabase()
1601	login.Init(db)
1602
1603	listener, err := openListener()
1604	if err != nil {
1605		log.Fatal(err)
1606	}
1607	go redeliverator()
1608
1609	debug := false
1610	getconfig("debug", &debug)
1611	readviews = templates.Load(debug,
1612		"views/honkpage.html",
1613		"views/honkfrags.html",
1614		"views/honkers.html",
1615		"views/hfcs.html",
1616		"views/combos.html",
1617		"views/honkform.html",
1618		"views/honk.html",
1619		"views/account.html",
1620		"views/about.html",
1621		"views/funzone.html",
1622		"views/login.html",
1623		"views/xzone.html",
1624		"views/header.html",
1625		"views/onts.html",
1626		"views/honkpage.js",
1627	)
1628	if !debug {
1629		assets := []string{"views/style.css", "views/local.css", "views/honkpage.js"}
1630		for _, s := range assets {
1631			savedassetparams[s] = getassetparam(s)
1632		}
1633	}
1634
1635	mux := mux.NewRouter()
1636	mux.Use(login.Checker)
1637
1638	posters := mux.Methods("POST").Subrouter()
1639	getters := mux.Methods("GET").Subrouter()
1640
1641	getters.HandleFunc("/", homepage)
1642	getters.HandleFunc("/home", homepage)
1643	getters.HandleFunc("/front", homepage)
1644	getters.HandleFunc("/robots.txt", nomoroboto)
1645	getters.HandleFunc("/rss", showrss)
1646	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
1647	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showhonk)
1648	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
1649	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
1650	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
1651	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
1652	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
1653	getters.HandleFunc("/a", avatate)
1654	getters.HandleFunc("/o", thelistingoftheontologies)
1655	getters.HandleFunc("/o/{name:.+}", showontology)
1656	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
1657	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
1658	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
1659	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
1660
1661	getters.HandleFunc("/style.css", servecss)
1662	getters.HandleFunc("/local.css", servecss)
1663	getters.HandleFunc("/honkpage.js", serveasset)
1664	getters.HandleFunc("/about", servehtml)
1665	getters.HandleFunc("/login", servehtml)
1666	posters.HandleFunc("/dologin", login.LoginFunc)
1667	getters.HandleFunc("/logout", login.LogoutFunc)
1668	getters.HandleFunc("/help/{name:[[:alnum:]_.-]+}", servehelp)
1669
1670	loggedin := mux.NewRoute().Subrouter()
1671	loggedin.Use(login.Required)
1672	loggedin.HandleFunc("/account", accountpage)
1673	loggedin.HandleFunc("/funzone", showfunzone)
1674	loggedin.HandleFunc("/chpass", dochpass)
1675	loggedin.HandleFunc("/atme", homepage)
1676	loggedin.HandleFunc("/hfcs", hfcspage)
1677	loggedin.HandleFunc("/xzone", xzone)
1678	loggedin.HandleFunc("/edit", edithonkpage)
1679	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
1680	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
1681	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
1682	loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
1683	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
1684	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
1685	loggedin.HandleFunc("/honkers", showhonkers)
1686	loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
1687	loggedin.HandleFunc("/h", showhonker)
1688	loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
1689	loggedin.HandleFunc("/c", showcombos)
1690	loggedin.HandleFunc("/t", showconvoy)
1691	loggedin.HandleFunc("/q", showsearch)
1692	loggedin.HandleFunc("/hydra", webhydra)
1693	loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
1694
1695	err = http.Serve(listener, mux)
1696	if err != nil {
1697		log.Fatal(err)
1698	}
1699}