all repos — honk @ 9dc4e7948bac0a47304cef6c3d51e28079939cd9

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/template"
  23	"io"
  24	"log"
  25	notrand "math/rand"
  26	"net/http"
  27	"net/url"
  28	"os"
  29	"os/signal"
  30	"regexp"
  31	"sort"
  32	"strconv"
  33	"strings"
  34	"time"
  35
  36	"github.com/gorilla/mux"
  37	"humungus.tedunangst.com/r/webs/cache"
  38	"humungus.tedunangst.com/r/webs/httpsig"
  39	"humungus.tedunangst.com/r/webs/junk"
  40	"humungus.tedunangst.com/r/webs/login"
  41	"humungus.tedunangst.com/r/webs/rss"
  42	"humungus.tedunangst.com/r/webs/templates"
  43)
  44
  45var readviews *templates.Template
  46
  47var userSep = "u"
  48var honkSep = "h"
  49
  50func getuserstyle(u *login.UserInfo) template.CSS {
  51	if u == nil {
  52		return ""
  53	}
  54	user, _ := butwhatabout(u.Username)
  55	if user.Options.SkinnyCSS {
  56		return "main { max-width: 700px; }"
  57	}
  58	return ""
  59}
  60
  61func getInfo(r *http.Request) map[string]interface{} {
  62	u := login.GetUserInfo(r)
  63	templinfo := make(map[string]interface{})
  64	templinfo["StyleParam"] = getassetparam(viewDir + "/views/style.css")
  65	templinfo["LocalStyleParam"] = getassetparam(viewDir + "/views/local.css")
  66	templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
  67	templinfo["UserStyle"] = getuserstyle(u)
  68	templinfo["ServerName"] = serverName
  69	templinfo["IconName"] = iconName
  70	templinfo["UserInfo"] = u
  71	templinfo["UserSep"] = userSep
  72	if u != nil {
  73		var combos []string
  74		combocache.Get(u.UserID, &combos)
  75		templinfo["Combos"] = combos
  76	}
  77	return templinfo
  78}
  79
  80func homepage(w http.ResponseWriter, r *http.Request) {
  81	templinfo := getInfo(r)
  82	u := login.GetUserInfo(r)
  83	var honks []*Honk
  84	var userid int64 = -1
  85
  86	templinfo["ServerMessage"] = serverMsg
  87	if u == nil || r.URL.Path == "/front" {
  88		switch r.URL.Path {
  89		case "/events":
  90			honks = geteventhonks(userid)
  91			templinfo["ServerMessage"] = "some recent and upcoming events"
  92		default:
  93			templinfo["ShowRSS"] = true
  94			honks = getpublichonks()
  95		}
  96	} else {
  97		userid = u.UserID
  98		switch r.URL.Path {
  99		case "/atme":
 100			templinfo["ServerMessage"] = "at me!"
 101			templinfo["PageName"] = "atme"
 102			honks = gethonksforme(userid, 0)
 103			honks = osmosis(honks, userid, false)
 104		case "/events":
 105			templinfo["ServerMessage"] = "some recent and upcoming events"
 106			templinfo["PageName"] = "events"
 107			honks = geteventhonks(userid)
 108			honks = osmosis(honks, userid, true)
 109		case "/first":
 110			templinfo["PageName"] = "first"
 111			honks = gethonksforuserfirstclass(userid, 0)
 112			honks = osmosis(honks, userid, true)
 113		case "/saved":
 114			templinfo["ServerMessage"] = "saved honks"
 115			templinfo["PageName"] = "saved"
 116			honks = getsavedhonks(userid, 0)
 117		default:
 118			templinfo["PageName"] = "home"
 119			honks = gethonksforuser(userid, 0)
 120			honks = osmosis(honks, userid, true)
 121		}
 122		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 123	}
 124
 125	honkpage(w, u, honks, templinfo)
 126}
 127
 128func showfunzone(w http.ResponseWriter, r *http.Request) {
 129	var emunames, memenames []string
 130	dir, err := os.Open(dataDir + "/emus")
 131	if err == nil {
 132		emunames, _ = dir.Readdirnames(0)
 133		dir.Close()
 134	}
 135	for i, e := range emunames {
 136		if len(e) > 4 {
 137			emunames[i] = e[:len(e)-4]
 138		}
 139	}
 140	dir, err = os.Open(dataDir + "/memes")
 141	if err == nil {
 142		memenames, _ = dir.Readdirnames(0)
 143		dir.Close()
 144	}
 145	templinfo := getInfo(r)
 146	templinfo["Emus"] = emunames
 147	templinfo["Memes"] = memenames
 148	err = readviews.Execute(w, "funzone.html", templinfo)
 149	if err != nil {
 150		log.Print(err)
 151	}
 152}
 153
 154func showrss(w http.ResponseWriter, r *http.Request) {
 155	name := mux.Vars(r)["name"]
 156
 157	var honks []*Honk
 158	if name != "" {
 159		honks = gethonksbyuser(name, false, 0)
 160	} else {
 161		honks = getpublichonks()
 162	}
 163	if len(honks) > 20 {
 164		honks = honks[0:20]
 165	}
 166	reverbolate(-1, honks)
 167
 168	home := fmt.Sprintf("https://%s/", serverName)
 169	base := home
 170	if name != "" {
 171		home += "u/" + name
 172		name += " "
 173	}
 174	feed := rss.Feed{
 175		Title:       name + "honk",
 176		Link:        home,
 177		Description: name + "honk rss",
 178		Image: &rss.Image{
 179			URL:   base + "icon.png",
 180			Title: name + "honk rss",
 181			Link:  home,
 182		},
 183	}
 184	var modtime time.Time
 185	for _, honk := range honks {
 186		if !firstclass(honk) {
 187			continue
 188		}
 189		desc := string(honk.HTML)
 190		if t := honk.Time; t != nil {
 191			desc += fmt.Sprintf(`<p>Time: %s`, t.StartTime.Local().Format("03:04PM EDT Mon Jan 02"))
 192			if t.Duration != 0 {
 193				desc += fmt.Sprintf(`<br>Duration: %s`, t.Duration)
 194			}
 195		}
 196		if p := honk.Place; p != nil {
 197			desc += string(templates.Sprintf(`<p>Location: <a href="%s">%s</a> %f %f`,
 198				p.Url, p.Name, p.Latitude, p.Longitude))
 199		}
 200		for _, d := range honk.Donks {
 201			desc += string(templates.Sprintf(`<p><a href="%s">Attachment: %s</a>`,
 202				d.URL, d.Name))
 203		}
 204
 205		feed.Items = append(feed.Items, &rss.Item{
 206			Title:       fmt.Sprintf("%s %s %s", honk.Username, honk.What, honk.XID),
 207			Description: rss.CData{Data: desc},
 208			Link:        honk.URL,
 209			PubDate:     honk.Date.Format(time.RFC1123),
 210			Guid:        &rss.Guid{IsPermaLink: true, Value: honk.URL},
 211		})
 212		if honk.Date.After(modtime) {
 213			modtime = honk.Date
 214		}
 215	}
 216	w.Header().Set("Cache-Control", "max-age=300")
 217	w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
 218
 219	err := feed.Write(w)
 220	if err != nil {
 221		log.Printf("error writing rss: %s", err)
 222	}
 223}
 224
 225func crappola(j junk.Junk) bool {
 226	t, _ := j.GetString("type")
 227	a, _ := j.GetString("actor")
 228	o, _ := j.GetString("object")
 229	if t == "Delete" && a == o {
 230		log.Printf("crappola from %s", a)
 231		return true
 232	}
 233	return false
 234}
 235
 236func ping(user *WhatAbout, who string) {
 237	var box *Box
 238	ok := boxofboxes.Get(who, &box)
 239	if !ok {
 240		log.Printf("no inbox to ping %s", who)
 241		return
 242	}
 243	j := junk.New()
 244	j["@context"] = itiswhatitis
 245	j["type"] = "Ping"
 246	j["id"] = user.URL + "/ping/" + xfiltrate()
 247	j["actor"] = user.URL
 248	j["to"] = who
 249	ki := ziggy(user.ID)
 250	if ki == nil {
 251		return
 252	}
 253	err := PostJunk(ki.keyname, ki.seckey, box.In, j)
 254	if err != nil {
 255		log.Printf("can't send ping: %s", err)
 256		return
 257	}
 258	log.Printf("sent ping to %s: %s", who, j["id"])
 259}
 260
 261func pong(user *WhatAbout, who string, obj string) {
 262	var box *Box
 263	ok := boxofboxes.Get(who, &box)
 264	if !ok {
 265		log.Printf("no inbox to pong %s", who)
 266		return
 267	}
 268	j := junk.New()
 269	j["@context"] = itiswhatitis
 270	j["type"] = "Pong"
 271	j["id"] = user.URL + "/pong/" + xfiltrate()
 272	j["actor"] = user.URL
 273	j["to"] = who
 274	j["object"] = obj
 275	ki := ziggy(user.ID)
 276	if ki == nil {
 277		return
 278	}
 279	err := PostJunk(ki.keyname, ki.seckey, box.In, j)
 280	if err != nil {
 281		log.Printf("can't send pong: %s", err)
 282		return
 283	}
 284}
 285
 286func inbox(w http.ResponseWriter, r *http.Request) {
 287	name := mux.Vars(r)["name"]
 288	user, err := butwhatabout(name)
 289	if err != nil {
 290		http.NotFound(w, r)
 291		return
 292	}
 293	if stealthmode(user.ID, r) {
 294		http.NotFound(w, r)
 295		return
 296	}
 297	var buf bytes.Buffer
 298	io.Copy(&buf, r.Body)
 299	payload := buf.Bytes()
 300	j, err := junk.FromBytes(payload)
 301	if err != nil {
 302		log.Printf("bad payload: %s", err)
 303		io.WriteString(os.Stdout, "bad payload\n")
 304		os.Stdout.Write(payload)
 305		io.WriteString(os.Stdout, "\n")
 306		return
 307	}
 308
 309	if crappola(j) {
 310		return
 311	}
 312	what, _ := j.GetString("type")
 313	if what == "Like" {
 314		return
 315	}
 316	who, _ := j.GetString("actor")
 317	if rejectactor(user.ID, who) {
 318		return
 319	}
 320
 321	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 322	if err != nil {
 323		log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
 324		if keyname != "" {
 325			log.Printf("bad signature from %s", keyname)
 326			io.WriteString(os.Stdout, "bad payload\n")
 327			os.Stdout.Write(payload)
 328			io.WriteString(os.Stdout, "\n")
 329		}
 330		http.Error(w, "what did you call me?", http.StatusTeapot)
 331		return
 332	}
 333	origin := keymatch(keyname, who)
 334	if origin == "" {
 335		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 336		return
 337	}
 338
 339	switch what {
 340	case "Ping":
 341		obj, _ := j.GetString("id")
 342		log.Printf("ping from %s: %s", who, obj)
 343		pong(user, who, obj)
 344	case "Pong":
 345		obj, _ := j.GetString("object")
 346		log.Printf("pong from %s: %s", who, obj)
 347	case "Follow":
 348		obj, _ := j.GetString("object")
 349		if obj != user.URL {
 350			log.Printf("can't follow %s", obj)
 351			return
 352		}
 353		log.Printf("updating honker follow: %s", who)
 354
 355		db := opendatabase()
 356		row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('dub', 'undub')", who, user.ID)
 357		var x string
 358		err = row.Scan(&x)
 359		if err != sql.ErrNoRows {
 360			log.Printf("duplicate follow request: %s", who)
 361			_, err = stmtUpdateFlavor.Exec("dub", user.ID, who, who, "undub")
 362			if err != nil {
 363				log.Printf("error updating honker: %s", err)
 364			}
 365		} else {
 366			stmtSaveDub.Exec(user.ID, who, who, "dub")
 367		}
 368		go rubadubdub(user, j)
 369	case "Accept":
 370		log.Printf("updating honker accept: %s", who)
 371		db := opendatabase()
 372		row := db.QueryRow("select name from honkers where userid = ? and xid = ? and flavor in ('presub')",
 373			user.ID, who)
 374		var name string
 375		err := row.Scan(&name)
 376		if err != nil {
 377			log.Printf("can't get honker name: %s", err)
 378			return
 379		}
 380		_, err = stmtUpdateFlavor.Exec("sub", user.ID, who, name, "presub")
 381		if err != nil {
 382			log.Printf("error updating honker: %s", err)
 383			return
 384		}
 385	case "Update":
 386		obj, ok := j.GetMap("object")
 387		if ok {
 388			what, _ := obj.GetString("type")
 389			switch what {
 390			case "Person":
 391				return
 392			case "Question":
 393				return
 394			case "Note":
 395				go xonksaver(user, j, origin)
 396				return
 397			}
 398		}
 399		log.Printf("unknown Update activity")
 400		fd, _ := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 401		j.Write(fd)
 402		io.WriteString(fd, "\n")
 403		fd.Close()
 404
 405	case "Undo":
 406		obj, ok := j.GetMap("object")
 407		if !ok {
 408			log.Printf("unknown undo no object")
 409			return
 410		}
 411		what, _ := obj.GetString("type")
 412		switch what {
 413		case "Follow":
 414			log.Printf("updating honker undo: %s", who)
 415			_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, who, "dub")
 416			if err != nil {
 417				log.Printf("error updating honker: %s", err)
 418				return
 419			}
 420		case "Announce":
 421			xid, _ := obj.GetString("object")
 422			log.Printf("undo announce: %s", xid)
 423		case "Like":
 424		default:
 425			log.Printf("unknown undo: %s", what)
 426		}
 427	default:
 428		go xonksaver(user, j, origin)
 429	}
 430}
 431
 432func serverinbox(w http.ResponseWriter, r *http.Request) {
 433	if stealthmode(serverUID, r) {
 434		http.NotFound(w, r)
 435		return
 436	}
 437	var buf bytes.Buffer
 438	io.Copy(&buf, r.Body)
 439	payload := buf.Bytes()
 440	j, err := junk.FromBytes(payload)
 441	if err != nil {
 442		log.Printf("bad payload: %s", err)
 443		io.WriteString(os.Stdout, "bad payload\n")
 444		os.Stdout.Write(payload)
 445		io.WriteString(os.Stdout, "\n")
 446		return
 447	}
 448	if crappola(j) {
 449		return
 450	}
 451	keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
 452	if err != nil {
 453		log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
 454		if keyname != "" {
 455			log.Printf("bad signature from %s", keyname)
 456			io.WriteString(os.Stdout, "bad payload\n")
 457			os.Stdout.Write(payload)
 458			io.WriteString(os.Stdout, "\n")
 459		}
 460		http.Error(w, "what did you call me?", http.StatusTeapot)
 461		return
 462	}
 463	user := getserveruser()
 464	who, _ := j.GetString("actor")
 465	origin := keymatch(keyname, who)
 466	if origin == "" {
 467		log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
 468		return
 469	}
 470	if rejectactor(user.ID, who) {
 471		return
 472	}
 473	re_ont := regexp.MustCompile("https://" + serverName + "/o/([[:alnum:]]+)")
 474	what, _ := j.GetString("type")
 475	log.Printf("server got a %s", what)
 476	switch what {
 477	case "Follow":
 478		obj, _ := j.GetString("object")
 479		if obj == user.URL {
 480			log.Printf("can't follow the server!")
 481			return
 482		}
 483		m := re_ont.FindStringSubmatch(obj)
 484		if len(m) != 2 {
 485			log.Printf("not sure how to handle this")
 486			return
 487		}
 488		ont := "#" + m[1]
 489		log.Printf("%s wants to follow %s", who, ont)
 490		db := opendatabase()
 491		row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", ont, who, user.ID)
 492		var x string
 493		err = row.Scan(&x)
 494		if err != sql.ErrNoRows {
 495			log.Printf("duplicate follow request: %s", who)
 496			_, err = stmtUpdateFlavor.Exec("dub", user.ID, who, ont, "undub")
 497			if err != nil {
 498				log.Printf("error updating honker: %s", err)
 499			}
 500		} else {
 501			stmtSaveDub.Exec(user.ID, ont, who, "dub")
 502		}
 503		go rubadubdub(user, j)
 504	case "Undo":
 505		obj, ok := j.GetMap("object")
 506		if !ok {
 507			log.Printf("unknown undo no object")
 508			return
 509		}
 510		what, _ := obj.GetString("type")
 511		if what != "Follow" {
 512			log.Printf("unknown undo: %s", what)
 513		}
 514		targ, _ := obj.GetString("object")
 515		m := re_ont.FindStringSubmatch(targ)
 516		if len(m) != 2 {
 517			log.Printf("not sure how to handle this")
 518			return
 519		}
 520		ont := "#" + m[1]
 521		log.Printf("updating honker undo: %s %s", who, ont)
 522		_, err = stmtUpdateFlavor.Exec("undub", user.ID, who, ont, "dub")
 523		if err != nil {
 524			log.Printf("error updating honker: %s", err)
 525			return
 526		}
 527	}
 528}
 529
 530func serveractor(w http.ResponseWriter, r *http.Request) {
 531	if stealthmode(serverUID, r) {
 532		http.NotFound(w, r)
 533		return
 534	}
 535	user := getserveruser()
 536	j := junkuser(user)
 537	w.Write(j)
 538}
 539
 540func ximport(w http.ResponseWriter, r *http.Request) {
 541	u := login.GetUserInfo(r)
 542	xid := strings.TrimSpace(r.FormValue("xid"))
 543	xonk := getxonk(u.UserID, xid)
 544	if xonk == nil {
 545		p, _ := investigate(xid)
 546		if p != nil {
 547			xid = p.XID
 548		}
 549		j, err := GetJunk(xid)
 550		if err != nil {
 551			http.Error(w, "error getting external object", http.StatusInternalServerError)
 552			log.Printf("error getting external object: %s", err)
 553			return
 554		}
 555		log.Printf("importing %s", xid)
 556		user, _ := butwhatabout(u.Username)
 557
 558		info, _ := somethingabout(j)
 559		if info == nil {
 560			xonk = xonksaver(user, j, originate(xid))
 561		} else if info.What == SomeActor {
 562			outbox, _ := j.GetString("outbox")
 563			gimmexonks(user, outbox)
 564			http.Redirect(w, r, "/h?xid="+url.QueryEscape(xid), http.StatusSeeOther)
 565			return
 566		} else if info.What == SomeCollection {
 567			gimmexonks(user, xid)
 568			http.Redirect(w, r, "/xzone", http.StatusSeeOther)
 569			return
 570		}
 571	}
 572	convoy := ""
 573	if xonk != nil {
 574		convoy = xonk.Convoy
 575	}
 576	http.Redirect(w, r, "/t?c="+url.QueryEscape(convoy), http.StatusSeeOther)
 577}
 578
 579func xzone(w http.ResponseWriter, r *http.Request) {
 580	u := login.GetUserInfo(r)
 581	rows, err := stmtRecentHonkers.Query(u.UserID, u.UserID)
 582	if err != nil {
 583		log.Printf("query err: %s", err)
 584		return
 585	}
 586	defer rows.Close()
 587	var honkers []Honker
 588	for rows.Next() {
 589		var xid string
 590		rows.Scan(&xid)
 591		honkers = append(honkers, Honker{XID: xid})
 592	}
 593	rows.Close()
 594	for i, _ := range honkers {
 595		_, honkers[i].Handle = handles(honkers[i].XID)
 596	}
 597	templinfo := getInfo(r)
 598	templinfo["XCSRF"] = login.GetCSRF("ximport", r)
 599	templinfo["Honkers"] = honkers
 600	err = readviews.Execute(w, "xzone.html", templinfo)
 601	if err != nil {
 602		log.Print(err)
 603	}
 604}
 605
 606var oldoutbox = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
 607	user, err := butwhatabout(name)
 608	if err != nil {
 609		return nil, false
 610	}
 611	honks := gethonksbyuser(name, false, 0)
 612	if len(honks) > 20 {
 613		honks = honks[0:20]
 614	}
 615
 616	var jonks []junk.Junk
 617	for _, h := range honks {
 618		j, _ := jonkjonk(user, h)
 619		jonks = append(jonks, j)
 620	}
 621
 622	j := junk.New()
 623	j["@context"] = itiswhatitis
 624	j["id"] = user.URL + "/outbox"
 625	j["attributedTo"] = user.URL
 626	j["type"] = "OrderedCollection"
 627	j["totalItems"] = len(jonks)
 628	j["orderedItems"] = jonks
 629
 630	return j.ToBytes(), true
 631}, Duration: 1 * time.Minute})
 632
 633func outbox(w http.ResponseWriter, r *http.Request) {
 634	name := mux.Vars(r)["name"]
 635	user, err := butwhatabout(name)
 636	if err != nil {
 637		http.NotFound(w, r)
 638		return
 639	}
 640	if stealthmode(user.ID, r) {
 641		http.NotFound(w, r)
 642		return
 643	}
 644	var j []byte
 645	ok := oldoutbox.Get(name, &j)
 646	if ok {
 647		w.Header().Set("Content-Type", theonetruename)
 648		w.Write(j)
 649	} else {
 650		http.NotFound(w, r)
 651	}
 652}
 653
 654var oldempties = cache.New(cache.Options{Filler: func(url string) ([]byte, bool) {
 655	colname := "/followers"
 656	if strings.HasSuffix(url, "/following") {
 657		colname = "/following"
 658	}
 659	user := fmt.Sprintf("https://%s%s", serverName, url[:len(url)-10])
 660	j := junk.New()
 661	j["@context"] = itiswhatitis
 662	j["id"] = user + colname
 663	j["attributedTo"] = user
 664	j["type"] = "OrderedCollection"
 665	j["totalItems"] = 0
 666	j["orderedItems"] = []junk.Junk{}
 667
 668	return j.ToBytes(), true
 669}})
 670
 671func emptiness(w http.ResponseWriter, r *http.Request) {
 672	name := mux.Vars(r)["name"]
 673	user, err := butwhatabout(name)
 674	if err != nil {
 675		http.NotFound(w, r)
 676		return
 677	}
 678	if stealthmode(user.ID, r) {
 679		http.NotFound(w, r)
 680		return
 681	}
 682	var j []byte
 683	ok := oldempties.Get(r.URL.Path, &j)
 684	if ok {
 685		w.Header().Set("Content-Type", theonetruename)
 686		w.Write(j)
 687	} else {
 688		http.NotFound(w, r)
 689	}
 690}
 691
 692func showuser(w http.ResponseWriter, r *http.Request) {
 693	name := mux.Vars(r)["name"]
 694	user, err := butwhatabout(name)
 695	if err != nil {
 696		log.Printf("user not found %s: %s", name, err)
 697		http.NotFound(w, r)
 698		return
 699	}
 700	if stealthmode(user.ID, r) {
 701		http.NotFound(w, r)
 702		return
 703	}
 704	if friendorfoe(r.Header.Get("Accept")) {
 705		j, ok := asjonker(name)
 706		if ok {
 707			w.Header().Set("Content-Type", theonetruename)
 708			w.Write(j)
 709		} else {
 710			http.NotFound(w, r)
 711		}
 712		return
 713	}
 714	u := login.GetUserInfo(r)
 715	honks := gethonksbyuser(name, u != nil && u.Username == name, 0)
 716	templinfo := getInfo(r)
 717	templinfo["Name"] = user.Name
 718	whatabout := user.About
 719	whatabout = markitzero(user.About)
 720	templinfo["WhatAbout"] = template.HTML(whatabout)
 721	templinfo["ServerMessage"] = ""
 722	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 723	honkpage(w, u, honks, templinfo)
 724}
 725
 726func showhonker(w http.ResponseWriter, r *http.Request) {
 727	u := login.GetUserInfo(r)
 728	name := mux.Vars(r)["name"]
 729	var honks []*Honk
 730	if name == "" {
 731		name = r.FormValue("xid")
 732		honks = gethonksbyxonker(u.UserID, name, 0)
 733	} else {
 734		honks = gethonksbyhonker(u.UserID, name, 0)
 735	}
 736	msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, name, name)
 737	templinfo := getInfo(r)
 738	templinfo["PageName"] = "honker"
 739	templinfo["PageArg"] = name
 740	templinfo["ServerMessage"] = msg
 741	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 742	honkpage(w, u, honks, templinfo)
 743}
 744
 745func showcombo(w http.ResponseWriter, r *http.Request) {
 746	name := mux.Vars(r)["name"]
 747	u := login.GetUserInfo(r)
 748	honks := gethonksbycombo(u.UserID, name, 0)
 749	honks = osmosis(honks, u.UserID, true)
 750	templinfo := getInfo(r)
 751	templinfo["PageName"] = "combo"
 752	templinfo["PageArg"] = name
 753	templinfo["ServerMessage"] = "honks by combo: " + name
 754	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 755	honkpage(w, u, honks, templinfo)
 756}
 757func showconvoy(w http.ResponseWriter, r *http.Request) {
 758	c := r.FormValue("c")
 759	u := login.GetUserInfo(r)
 760	honks := gethonksbyconvoy(u.UserID, c, 0)
 761	templinfo := getInfo(r)
 762	if len(honks) > 0 {
 763		templinfo["TopHID"] = honks[0].ID
 764	}
 765	honks = osmosis(honks, u.UserID, false)
 766	reversehonks(honks)
 767	templinfo["PageName"] = "convoy"
 768	templinfo["PageArg"] = c
 769	templinfo["ServerMessage"] = "honks in convoy: " + c
 770	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 771	honkpage(w, u, honks, templinfo)
 772}
 773func showsearch(w http.ResponseWriter, r *http.Request) {
 774	q := r.FormValue("q")
 775	u := login.GetUserInfo(r)
 776	honks := gethonksbysearch(u.UserID, q, 0)
 777	templinfo := getInfo(r)
 778	templinfo["PageName"] = "search"
 779	templinfo["PageArg"] = q
 780	templinfo["ServerMessage"] = "honks for search: " + q
 781	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 782	honkpage(w, u, honks, templinfo)
 783}
 784func showontology(w http.ResponseWriter, r *http.Request) {
 785	name := mux.Vars(r)["name"]
 786	u := login.GetUserInfo(r)
 787	var userid int64 = -1
 788	if u != nil {
 789		userid = u.UserID
 790	}
 791	honks := gethonksbyontology(userid, "#"+name, 0)
 792	if friendorfoe(r.Header.Get("Accept")) {
 793		if len(honks) > 40 {
 794			honks = honks[0:40]
 795		}
 796
 797		var xids []string
 798		for _, h := range honks {
 799			xids = append(xids, h.XID)
 800		}
 801
 802		user := getserveruser()
 803
 804		j := junk.New()
 805		j["@context"] = itiswhatitis
 806		j["id"] = fmt.Sprintf("https://%s/o/%s", serverName, name)
 807		j["name"] = "#" + name
 808		j["attributedTo"] = user.URL
 809		j["type"] = "OrderedCollection"
 810		j["totalItems"] = len(xids)
 811		j["orderedItems"] = xids
 812
 813		j.Write(w)
 814		return
 815	}
 816
 817	templinfo := getInfo(r)
 818	templinfo["ServerMessage"] = "honks by ontology: " + name
 819	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
 820	honkpage(w, u, honks, templinfo)
 821}
 822
 823type Ont struct {
 824	Name  string
 825	Count int64
 826}
 827
 828func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
 829	u := login.GetUserInfo(r)
 830	var userid int64 = -1
 831	if u != nil {
 832		userid = u.UserID
 833	}
 834	rows, err := stmtAllOnts.Query(userid)
 835	if err != nil {
 836		log.Printf("selection error: %s", err)
 837		return
 838	}
 839	defer rows.Close()
 840	var onts []Ont
 841	for rows.Next() {
 842		var o Ont
 843		err := rows.Scan(&o.Name, &o.Count)
 844		if err != nil {
 845			log.Printf("error scanning ont: %s", err)
 846			continue
 847		}
 848		if len(o.Name) > 24 {
 849			continue
 850		}
 851		o.Name = o.Name[1:]
 852		onts = append(onts, o)
 853	}
 854	sort.Slice(onts, func(i, j int) bool {
 855		return onts[i].Name < onts[j].Name
 856	})
 857	if u == nil {
 858		w.Header().Set("Cache-Control", "max-age=300")
 859	}
 860	templinfo := getInfo(r)
 861	templinfo["Onts"] = onts
 862	err = readviews.Execute(w, "onts.html", templinfo)
 863	if err != nil {
 864		log.Print(err)
 865	}
 866}
 867
 868type Track struct {
 869	xid string
 870	who string
 871}
 872
 873func savetracks(tracks map[string][]string) {
 874	db := opendatabase()
 875	tx, err := db.Begin()
 876	if err != nil {
 877		log.Printf("savetracks begin error: %s", err)
 878		return
 879	}
 880	defer func() {
 881		err := tx.Commit()
 882		if err != nil {
 883			log.Printf("savetracks commit error: %s", err)
 884		}
 885
 886	}()
 887	stmtGetTracks, err := tx.Prepare("select fetches from tracks where xid = ?")
 888	if err != nil {
 889		log.Printf("savetracks error: %s", err)
 890		return
 891	}
 892	stmtNewTracks, err := tx.Prepare("insert into tracks (xid, fetches) values (?, ?)")
 893	if err != nil {
 894		log.Printf("savetracks error: %s", err)
 895		return
 896	}
 897	stmtUpdateTracks, err := tx.Prepare("update tracks set fetches = ? where xid = ?")
 898	if err != nil {
 899		log.Printf("savetracks error: %s", err)
 900		return
 901	}
 902	count := 0
 903	for xid, f := range tracks {
 904		count += len(f)
 905		var prev string
 906		row := stmtGetTracks.QueryRow(xid)
 907		err := row.Scan(&prev)
 908		if err == sql.ErrNoRows {
 909			f = oneofakind(f)
 910			stmtNewTracks.Exec(xid, strings.Join(f, " "))
 911		} else if err == nil {
 912			all := append(strings.Split(prev, " "), f...)
 913			all = oneofakind(all)
 914			stmtUpdateTracks.Exec(strings.Join(all, " "))
 915		} else {
 916			log.Printf("savetracks error: %s", err)
 917		}
 918	}
 919	log.Printf("saved %d new fetches", count)
 920}
 921
 922var trackchan = make(chan Track)
 923
 924func tracker() {
 925	timeout := 4 * time.Minute
 926	sleeper := time.NewTimer(timeout)
 927	tracks := make(map[string][]string)
 928	for {
 929		select {
 930		case track := <-trackchan:
 931			tracks[track.xid] = append(tracks[track.xid], track.who)
 932		case <-sleeper.C:
 933			if len(tracks) > 0 {
 934				go savetracks(tracks)
 935				tracks = make(map[string][]string)
 936			}
 937			sleeper.Reset(timeout)
 938		case <-endoftheworld:
 939			if len(tracks) > 0 {
 940				savetracks(tracks)
 941			}
 942			readyalready <- true
 943		}
 944	}
 945}
 946
 947var re_keyholder = regexp.MustCompile(`keyId="([^"]+)"`)
 948
 949func trackback(xid string, r *http.Request) {
 950	agent := r.UserAgent()
 951	who := originate(agent)
 952	sig := r.Header.Get("Signature")
 953	if sig != "" {
 954		m := re_keyholder.FindStringSubmatch(sig)
 955		if len(m) == 2 {
 956			who = m[1]
 957		}
 958	}
 959	if who != "" {
 960		trackchan <- Track{xid: xid, who: who}
 961	}
 962}
 963
 964func showonehonk(w http.ResponseWriter, r *http.Request) {
 965	name := mux.Vars(r)["name"]
 966	user, err := butwhatabout(name)
 967	if err != nil {
 968		http.NotFound(w, r)
 969		return
 970	}
 971	if stealthmode(user.ID, r) {
 972		http.NotFound(w, r)
 973		return
 974	}
 975	xid := fmt.Sprintf("https://%s%s", serverName, r.URL.Path)
 976
 977	if friendorfoe(r.Header.Get("Accept")) {
 978		j, ok := gimmejonk(xid)
 979		if ok {
 980			trackback(xid, r)
 981			w.Header().Set("Content-Type", theonetruename)
 982			w.Write(j)
 983		} else {
 984			http.NotFound(w, r)
 985		}
 986		return
 987	}
 988	honk := getxonk(user.ID, xid)
 989	if honk == nil {
 990		http.NotFound(w, r)
 991		return
 992	}
 993	u := login.GetUserInfo(r)
 994	if u != nil && u.UserID != user.ID {
 995		u = nil
 996	}
 997	if !honk.Public {
 998		if u == nil {
 999			http.NotFound(w, r)
1000			return
1001
1002		}
1003		templinfo := getInfo(r)
1004		templinfo["ServerMessage"] = "one honk maybe more"
1005		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1006		honkpage(w, u, []*Honk{honk}, templinfo)
1007		return
1008	}
1009	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1010	reversehonks(rawhonks)
1011	var honks []*Honk
1012	for _, h := range rawhonks {
1013		if h.XID == xid && len(honks) != 0 {
1014			h.Style += " glow"
1015		}
1016		if h.Public && (h.Whofore == 2 || h.IsAcked()) {
1017			honks = append(honks, h)
1018		}
1019	}
1020
1021	templinfo := getInfo(r)
1022	templinfo["ServerMessage"] = "one honk maybe more"
1023	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1024	honkpage(w, u, honks, templinfo)
1025}
1026
1027func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo map[string]interface{}) {
1028	var userid int64 = -1
1029	if u != nil {
1030		userid = u.UserID
1031	}
1032	reverbolate(userid, honks)
1033	templinfo["Honks"] = honks
1034	if templinfo["TopHID"] == nil {
1035		if len(honks) > 0 {
1036			templinfo["TopHID"] = honks[0].ID
1037		} else {
1038			templinfo["TopHID"] = 0
1039		}
1040	}
1041	if u == nil {
1042		w.Header().Set("Cache-Control", "max-age=60")
1043	}
1044	err := readviews.Execute(w, "honkpage.html", templinfo)
1045	if err != nil {
1046		log.Print(err)
1047	}
1048}
1049
1050func saveuser(w http.ResponseWriter, r *http.Request) {
1051	whatabout := r.FormValue("whatabout")
1052	u := login.GetUserInfo(r)
1053	db := opendatabase()
1054	var options UserOptions
1055	if r.FormValue("skinny") == "skinny" {
1056		options.SkinnyCSS = true
1057	}
1058	j, err := jsonify(options)
1059	if err == nil {
1060		_, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)
1061	}
1062	if err != nil {
1063		log.Printf("error bouting what: %s", err)
1064	}
1065	somenamedusers.Clear(u.Username)
1066	somenumberedusers.Clear(u.UserID)
1067
1068	http.Redirect(w, r, "/account", http.StatusSeeOther)
1069}
1070
1071func submitbonk(w http.ResponseWriter, r *http.Request) {
1072	xid := r.FormValue("xid")
1073	userinfo := login.GetUserInfo(r)
1074	user, _ := butwhatabout(userinfo.Username)
1075
1076	log.Printf("bonking %s", xid)
1077
1078	xonk := getxonk(userinfo.UserID, xid)
1079	if xonk == nil {
1080		return
1081	}
1082	if !xonk.Public {
1083		return
1084	}
1085	if xonk.IsBonked() {
1086		return
1087	}
1088	donksforhonks([]*Honk{xonk})
1089
1090	_, err := stmtUpdateFlags.Exec(flagIsBonked, xonk.ID)
1091	if err != nil {
1092		log.Printf("error acking bonk: %s", err)
1093	}
1094
1095	oonker := xonk.Oonker
1096	if oonker == "" {
1097		oonker = xonk.Honker
1098	}
1099	dt := time.Now().UTC()
1100	bonk := &Honk{
1101		UserID:   userinfo.UserID,
1102		Username: userinfo.Username,
1103		What:     "bonk",
1104		Honker:   user.URL,
1105		Oonker:   oonker,
1106		XID:      xonk.XID,
1107		RID:      xonk.RID,
1108		Noise:    xonk.Noise,
1109		Precis:   xonk.Precis,
1110		URL:      xonk.URL,
1111		Date:     dt,
1112		Donks:    xonk.Donks,
1113		Whofore:  2,
1114		Convoy:   xonk.Convoy,
1115		Audience: []string{thewholeworld, oonker},
1116		Public:   true,
1117		Format:   "html",
1118		Place:    xonk.Place,
1119		Onts:     xonk.Onts,
1120		Time:     xonk.Time,
1121	}
1122
1123	err = savehonk(bonk)
1124	if err != nil {
1125		log.Printf("uh oh")
1126		return
1127	}
1128
1129	go honkworldwide(user, bonk)
1130
1131	if r.FormValue("js") != "1" {
1132		templinfo := getInfo(r)
1133		templinfo["ServerMessage"] = "Bonked!"
1134		err = readviews.Execute(w, "msg.html", templinfo)
1135		if err != nil {
1136			log.Print(err)
1137		}
1138	}
1139}
1140
1141func sendzonkofsorts(xonk *Honk, user *WhatAbout, what string) {
1142	zonk := &Honk{
1143		What:     what,
1144		XID:      xonk.XID,
1145		Date:     time.Now().UTC(),
1146		Audience: oneofakind(xonk.Audience),
1147	}
1148	zonk.Public = loudandproud(zonk.Audience)
1149
1150	log.Printf("announcing %sed honk: %s", what, xonk.XID)
1151	go honkworldwide(user, zonk)
1152}
1153
1154func zonkit(w http.ResponseWriter, r *http.Request) {
1155	wherefore := r.FormValue("wherefore")
1156	what := r.FormValue("what")
1157	userinfo := login.GetUserInfo(r)
1158	user, _ := butwhatabout(userinfo.Username)
1159
1160	if wherefore == "save" {
1161		xonk := getxonk(userinfo.UserID, what)
1162		if xonk != nil {
1163			_, err := stmtUpdateFlags.Exec(flagIsSaved, xonk.ID)
1164			if err != nil {
1165				log.Printf("error saving: %s", err)
1166			}
1167		}
1168		return
1169	}
1170
1171	if wherefore == "unsave" {
1172		xonk := getxonk(userinfo.UserID, what)
1173		if xonk != nil {
1174			_, err := stmtClearFlags.Exec(flagIsSaved, xonk.ID)
1175			if err != nil {
1176				log.Printf("error unsaving: %s", err)
1177			}
1178		}
1179		return
1180	}
1181
1182	// my hammer is too big, oh well
1183	defer oldjonks.Flush()
1184
1185	if wherefore == "ack" {
1186		xonk := getxonk(userinfo.UserID, what)
1187		if xonk != nil && !xonk.IsAcked() {
1188			_, err := stmtUpdateFlags.Exec(flagIsAcked, xonk.ID)
1189			if err != nil {
1190				log.Printf("error acking: %s", err)
1191			}
1192			sendzonkofsorts(xonk, user, "ack")
1193		}
1194		return
1195	}
1196
1197	if wherefore == "deack" {
1198		xonk := getxonk(userinfo.UserID, what)
1199		if xonk != nil && xonk.IsAcked() {
1200			_, err := stmtClearFlags.Exec(flagIsAcked, xonk.ID)
1201			if err != nil {
1202				log.Printf("error deacking: %s", err)
1203			}
1204			sendzonkofsorts(xonk, user, "deack")
1205		}
1206		return
1207	}
1208
1209	if wherefore == "unbonk" {
1210		xonk := getbonk(userinfo.UserID, what)
1211		if xonk != nil {
1212			deletehonk(xonk.ID)
1213			xonk = getxonk(userinfo.UserID, what)
1214			_, err := stmtClearFlags.Exec(flagIsBonked, xonk.ID)
1215			if err != nil {
1216				log.Printf("error unbonking: %s", err)
1217			}
1218			sendzonkofsorts(xonk, user, "unbonk")
1219		}
1220		return
1221	}
1222
1223	if wherefore == "untag" {
1224		xonk := getxonk(userinfo.UserID, what)
1225		if xonk != nil {
1226			_, err := stmtUpdateFlags.Exec(flagIsUntagged, xonk.ID)
1227			if err != nil {
1228				log.Printf("error untagging: %s", err)
1229			}
1230		}
1231		var badparents map[string]bool
1232		untagged.GetAndLock(userinfo.UserID, &badparents)
1233		badparents[what] = true
1234		untagged.Unlock()
1235		return
1236	}
1237
1238	log.Printf("zonking %s %s", wherefore, what)
1239	if wherefore == "zonk" {
1240		xonk := getxonk(userinfo.UserID, what)
1241		if xonk != nil {
1242			deletehonk(xonk.ID)
1243			if xonk.Whofore == 2 || xonk.Whofore == 3 {
1244				sendzonkofsorts(xonk, user, "zonk")
1245			}
1246		}
1247	}
1248	_, err := stmtSaveZonker.Exec(userinfo.UserID, what, wherefore)
1249	if err != nil {
1250		log.Printf("error saving zonker: %s", err)
1251		return
1252	}
1253}
1254
1255func edithonkpage(w http.ResponseWriter, r *http.Request) {
1256	u := login.GetUserInfo(r)
1257	user, _ := butwhatabout(u.Username)
1258	xid := r.FormValue("xid")
1259	honk := getxonk(u.UserID, xid)
1260	if !canedithonk(user, honk) {
1261		http.Error(w, "no editing that please", http.StatusInternalServerError)
1262		return
1263	}
1264
1265	noise := honk.Noise
1266
1267	honks := []*Honk{honk}
1268	donksforhonks(honks)
1269	reverbolate(u.UserID, honks)
1270	templinfo := getInfo(r)
1271	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1272	templinfo["Honks"] = honks
1273	templinfo["Noise"] = noise
1274	templinfo["SavedPlace"] = honk.Place
1275	templinfo["ServerMessage"] = "honk edit"
1276	templinfo["IsPreview"] = true
1277	templinfo["UpdateXID"] = honk.XID
1278	if len(honk.Donks) > 0 {
1279		templinfo["SavedFile"] = honk.Donks[0].XID
1280	}
1281	err := readviews.Execute(w, "honkpage.html", templinfo)
1282	if err != nil {
1283		log.Print(err)
1284	}
1285}
1286
1287func newhonkpage(w http.ResponseWriter, r *http.Request) {
1288	u := login.GetUserInfo(r)
1289	rid := r.FormValue("rid")
1290	noise := ""
1291
1292	xonk := getxonk(u.UserID, rid)
1293	if xonk != nil {
1294		_, replto := handles(xonk.Honker)
1295		if replto != "" {
1296			noise = "@" + replto + " "
1297		}
1298	}
1299
1300	templinfo := getInfo(r)
1301	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1302	templinfo["InReplyTo"] = rid
1303	templinfo["Noise"] = noise
1304	templinfo["ServerMessage"] = "compose honk"
1305	templinfo["IsPreview"] = true
1306	err := readviews.Execute(w, "honkpage.html", templinfo)
1307	if err != nil {
1308		log.Print(err)
1309	}
1310}
1311
1312func canedithonk(user *WhatAbout, honk *Honk) bool {
1313	if honk == nil || honk.Honker != user.URL || honk.What == "bonk" {
1314		return false
1315	}
1316	return true
1317}
1318
1319// what a hot mess this function is
1320func submithonk(w http.ResponseWriter, r *http.Request) {
1321	rid := r.FormValue("rid")
1322	noise := r.FormValue("noise")
1323
1324	userinfo := login.GetUserInfo(r)
1325	user, _ := butwhatabout(userinfo.Username)
1326
1327	dt := time.Now().UTC()
1328	updatexid := r.FormValue("updatexid")
1329	var honk *Honk
1330	if updatexid != "" {
1331		honk = getxonk(userinfo.UserID, updatexid)
1332		if !canedithonk(user, honk) {
1333			http.Error(w, "no editing that please", http.StatusInternalServerError)
1334			return
1335		}
1336		honk.Date = dt
1337		honk.What = "update"
1338		honk.Format = "markdown"
1339	} else {
1340		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, xfiltrate())
1341		what := "honk"
1342		if rid != "" {
1343			what = "tonk"
1344		}
1345		honk = &Honk{
1346			UserID:   userinfo.UserID,
1347			Username: userinfo.Username,
1348			What:     what,
1349			Honker:   user.URL,
1350			XID:      xid,
1351			Date:     dt,
1352			Format:   "markdown",
1353		}
1354	}
1355
1356	noise = strings.Replace(noise, "\r", "", -1)
1357	noise = quickrename(noise, userinfo.UserID)
1358	noise = hooterize(noise)
1359	honk.Noise = noise
1360	translate(honk, false)
1361
1362	var convoy string
1363	if rid != "" {
1364		xonk := getxonk(userinfo.UserID, rid)
1365		if xonk != nil {
1366			if xonk.Public {
1367				honk.Audience = append(honk.Audience, xonk.Audience...)
1368			}
1369			convoy = xonk.Convoy
1370		} else {
1371			xonkaud, c := whosthere(rid)
1372			honk.Audience = append(honk.Audience, xonkaud...)
1373			convoy = c
1374		}
1375		for i, a := range honk.Audience {
1376			if a == thewholeworld {
1377				honk.Audience[0], honk.Audience[i] = honk.Audience[i], honk.Audience[0]
1378				break
1379			}
1380		}
1381		honk.RID = rid
1382	} else {
1383		honk.Audience = []string{thewholeworld}
1384	}
1385	if honk.Noise != "" && honk.Noise[0] == '@' {
1386		honk.Audience = append(grapevine(honk.Noise), honk.Audience...)
1387	} else {
1388		honk.Audience = append(honk.Audience, grapevine(honk.Noise)...)
1389	}
1390
1391	if convoy == "" {
1392		convoy = "data:,electrichonkytonk-" + xfiltrate()
1393	}
1394	butnottooloud(honk.Audience)
1395	honk.Audience = oneofakind(honk.Audience)
1396	if len(honk.Audience) == 0 {
1397		log.Printf("honk to nowhere")
1398		http.Error(w, "honk to nowhere...", http.StatusNotFound)
1399		return
1400	}
1401	honk.Public = loudandproud(honk.Audience)
1402	honk.Convoy = convoy
1403
1404	donkxid := r.FormValue("donkxid")
1405	if donkxid == "" {
1406		file, filehdr, err := r.FormFile("donk")
1407		if err == nil {
1408			var buf bytes.Buffer
1409			io.Copy(&buf, file)
1410			file.Close()
1411			data := buf.Bytes()
1412			xid := xfiltrate()
1413			var media, name string
1414			img, err := shrinkit(data)
1415			if err == nil {
1416				data = img.Data
1417				format := img.Format
1418				media = "image/" + format
1419				if format == "jpeg" {
1420					format = "jpg"
1421				}
1422				name = xid + "." + format
1423				xid = name
1424			} else {
1425				ct := http.DetectContentType(data)
1426				switch ct {
1427				case "application/pdf":
1428					maxsize := 1000000
1429					if len(data) > maxsize {
1430						log.Printf("bad image: %s too much pdf: %d", err, len(data))
1431						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1432						return
1433					}
1434					media = ct
1435					xid += ".pdf"
1436					name = filehdr.Filename
1437					if name == "" {
1438						name = xid
1439					}
1440				default:
1441					maxsize := 100000
1442					if len(data) > maxsize {
1443						log.Printf("bad image: %s too much text: %d", err, len(data))
1444						http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1445						return
1446					}
1447					for i := 0; i < len(data); i++ {
1448						if data[i] < 32 && data[i] != '\t' && data[i] != '\r' && data[i] != '\n' {
1449							log.Printf("bad image: %s not text: %d", err, data[i])
1450							http.Error(w, "didn't like your attachment", http.StatusUnsupportedMediaType)
1451							return
1452						}
1453					}
1454					media = "text/plain"
1455					xid += ".txt"
1456					name = filehdr.Filename
1457					if name == "" {
1458						name = xid
1459					}
1460				}
1461			}
1462			desc := strings.TrimSpace(r.FormValue("donkdesc"))
1463			if desc == "" {
1464				desc = name
1465			}
1466			url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1467			fileid, err := savefile(xid, name, desc, url, media, true, data)
1468			if err != nil {
1469				log.Printf("unable to save image: %s", err)
1470				return
1471			}
1472			d := &Donk{
1473				FileID: fileid,
1474				XID:    xid,
1475				Desc:   desc,
1476				URL:    url,
1477				Local:  true,
1478			}
1479			honk.Donks = append(honk.Donks, d)
1480			donkxid = xid
1481		}
1482	} else {
1483		xid := donkxid
1484		url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
1485		donk := finddonk(url)
1486		if donk != nil {
1487			honk.Donks = append(honk.Donks, donk)
1488		} else {
1489			log.Printf("can't find file: %s", xid)
1490		}
1491	}
1492	herd := herdofemus(noise)
1493	for _, e := range herd {
1494		donk := savedonk(e.ID, e.Name, e.Name, "image/png", true)
1495		if donk != nil {
1496			donk.Name = e.Name
1497			honk.Donks = append(honk.Donks, donk)
1498		}
1499	}
1500	memetize(honk)
1501	imaginate(honk)
1502
1503	placename := strings.TrimSpace(r.FormValue("placename"))
1504	placelat := strings.TrimSpace(r.FormValue("placelat"))
1505	placelong := strings.TrimSpace(r.FormValue("placelong"))
1506	placeurl := strings.TrimSpace(r.FormValue("placeurl"))
1507	if placename != "" || placelat != "" || placelong != "" || placeurl != "" {
1508		p := new(Place)
1509		p.Name = placename
1510		p.Latitude, _ = strconv.ParseFloat(placelat, 64)
1511		p.Longitude, _ = strconv.ParseFloat(placelong, 64)
1512		p.Url = placeurl
1513		honk.Place = p
1514	}
1515	timestart := strings.TrimSpace(r.FormValue("timestart"))
1516	if timestart != "" {
1517		t := new(Time)
1518		now := time.Now().Local()
1519		for _, layout := range []string{"2006-01-02 3:04pm", "2006-01-02 15:04", "3:04pm", "15:04"} {
1520			start, err := time.ParseInLocation(layout, timestart, now.Location())
1521			if err == nil {
1522				if start.Year() == 0 {
1523					start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
1524				}
1525				t.StartTime = start
1526				break
1527			}
1528		}
1529		timeend := r.FormValue("timeend")
1530		dur := parseDuration(timeend)
1531		if dur != 0 {
1532			t.Duration = Duration(dur)
1533		}
1534		if !t.StartTime.IsZero() {
1535			honk.What = "event"
1536			honk.Time = t
1537		}
1538	}
1539
1540	if honk.Public {
1541		honk.Whofore = 2
1542	} else {
1543		honk.Whofore = 3
1544	}
1545
1546	// back to markdown
1547	honk.Noise = noise
1548
1549	if r.FormValue("preview") == "preview" {
1550		honks := []*Honk{honk}
1551		reverbolate(userinfo.UserID, honks)
1552		templinfo := getInfo(r)
1553		templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1554		templinfo["Honks"] = honks
1555		templinfo["InReplyTo"] = r.FormValue("rid")
1556		templinfo["Noise"] = r.FormValue("noise")
1557		templinfo["SavedFile"] = donkxid
1558		templinfo["IsPreview"] = true
1559		templinfo["UpdateXID"] = updatexid
1560		templinfo["ServerMessage"] = "honk preview"
1561		err := readviews.Execute(w, "honkpage.html", templinfo)
1562		if err != nil {
1563			log.Print(err)
1564		}
1565		return
1566	}
1567
1568	if updatexid != "" {
1569		updatehonk(honk)
1570		oldjonks.Clear(honk.XID)
1571	} else {
1572		err := savehonk(honk)
1573		if err != nil {
1574			log.Printf("uh oh")
1575			return
1576		}
1577	}
1578
1579	// reload for consistency
1580	honk.Donks = nil
1581	donksforhonks([]*Honk{honk})
1582
1583	go honkworldwide(user, honk)
1584
1585	http.Redirect(w, r, honk.XID[len(serverName)+8:], http.StatusSeeOther)
1586}
1587
1588func showhonkers(w http.ResponseWriter, r *http.Request) {
1589	userinfo := login.GetUserInfo(r)
1590	templinfo := getInfo(r)
1591	templinfo["Honkers"] = gethonkers(userinfo.UserID)
1592	templinfo["HonkerCSRF"] = login.GetCSRF("submithonker", r)
1593	err := readviews.Execute(w, "honkers.html", templinfo)
1594	if err != nil {
1595		log.Print(err)
1596	}
1597}
1598
1599var combocache = cache.New(cache.Options{Filler: func(userid int64) ([]string, bool) {
1600	honkers := gethonkers(userid)
1601	var combos []string
1602	for _, h := range honkers {
1603		combos = append(combos, h.Combos...)
1604	}
1605	for i, c := range combos {
1606		if c == "-" {
1607			combos[i] = ""
1608		}
1609	}
1610	combos = oneofakind(combos)
1611	sort.Strings(combos)
1612	return combos, true
1613}, Invalidator: &honkerinvalidator})
1614
1615func showcombos(w http.ResponseWriter, r *http.Request) {
1616	userinfo := login.GetUserInfo(r)
1617	var combos []string
1618	combocache.Get(userinfo.UserID, &combos)
1619	templinfo := getInfo(r)
1620	err := readviews.Execute(w, "combos.html", templinfo)
1621	if err != nil {
1622		log.Print(err)
1623	}
1624}
1625
1626func submithonker(w http.ResponseWriter, r *http.Request) {
1627	u := login.GetUserInfo(r)
1628	name := strings.TrimSpace(r.FormValue("name"))
1629	url := strings.TrimSpace(r.FormValue("url"))
1630	peep := r.FormValue("peep")
1631	combos := strings.TrimSpace(r.FormValue("combos"))
1632	combos = " " + combos + " "
1633	honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0)
1634
1635	defer honkerinvalidator.Clear(u.UserID)
1636
1637	if honkerid > 0 {
1638		goodbye := r.FormValue("goodbye")
1639		if goodbye == "F" {
1640			db := opendatabase()
1641			row := db.QueryRow("select xid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1642				honkerid, u.UserID)
1643			err := row.Scan(&url)
1644			if err != nil {
1645				log.Printf("can't get honker xid: %s", err)
1646				return
1647			}
1648			log.Printf("unsubscribing from %s", url)
1649			user, _ := butwhatabout(u.Username)
1650			_, err = stmtUpdateFlavor.Exec("unsub", u.UserID, url, name, "sub")
1651			if err != nil {
1652				log.Printf("error updating honker: %s", err)
1653				return
1654			}
1655			go itakeitallback(user, url)
1656
1657			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1658			return
1659		}
1660		if goodbye == "X" {
1661			var owner string
1662			db := opendatabase()
1663			row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep')",
1664				honkerid, u.UserID)
1665			err := row.Scan(&url, &owner)
1666			if err != nil {
1667				log.Printf("can't get honker xid: %s", err)
1668				return
1669			}
1670			log.Printf("resubscribing to %s", url)
1671			user, _ := butwhatabout(u.Username)
1672			_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, name, "unsub")
1673			if err == nil {
1674				_, err = stmtUpdateFlavor.Exec("presub", u.UserID, url, name, "peep")
1675			}
1676			if err != nil {
1677				log.Printf("error updating honker: %s", err)
1678				return
1679			}
1680			go subsub(user, url, owner)
1681
1682			http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1683			return
1684		}
1685		_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID)
1686		if err != nil {
1687			log.Printf("update honker err: %s", err)
1688			return
1689		}
1690		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1691		return
1692	}
1693
1694	if url == "" {
1695		http.Error(w, "subscribing to nothing?", http.StatusInternalServerError)
1696		return
1697	}
1698
1699	flavor := "presub"
1700	if peep == "peep" {
1701		flavor = "peep"
1702	}
1703
1704	if url[0] == '#' {
1705		flavor = "peep"
1706		if name == "" {
1707			name = url
1708		}
1709		_, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, url)
1710		if err != nil {
1711			log.Print(err)
1712			return
1713		}
1714		http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1715		return
1716	}
1717
1718	info, err := investigate(url)
1719	if err != nil {
1720		http.Error(w, "error investigating: "+err.Error(), http.StatusInternalServerError)
1721		log.Printf("failed to investigate honker: %s", err)
1722		return
1723	}
1724	url = info.XID
1725
1726	db := opendatabase()
1727	row := db.QueryRow("select xid from honkers where xid = ? and userid = ? and flavor in ('sub', 'unsub', 'peep')", url, u.UserID)
1728	var x string
1729	err = row.Scan(&x)
1730	if err != sql.ErrNoRows {
1731		http.Error(w, "it seems you are already subscribed to them", http.StatusInternalServerError)
1732		if err != nil {
1733			log.Printf("honker scan err: %s", err)
1734		}
1735		return
1736	}
1737
1738	if name == "" {
1739		name = info.Name
1740	}
1741	_, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, info.Owner)
1742	if err != nil {
1743		log.Print(err)
1744		return
1745	}
1746	if flavor == "presub" {
1747		user, _ := butwhatabout(u.Username)
1748		go subsub(user, url, info.Owner)
1749	}
1750	http.Redirect(w, r, "/honkers", http.StatusSeeOther)
1751}
1752
1753func hfcspage(w http.ResponseWriter, r *http.Request) {
1754	userinfo := login.GetUserInfo(r)
1755
1756	filters := getfilters(userinfo.UserID, filtAny)
1757
1758	templinfo := getInfo(r)
1759	templinfo["Filters"] = filters
1760	templinfo["FilterCSRF"] = login.GetCSRF("filter", r)
1761	err := readviews.Execute(w, "hfcs.html", templinfo)
1762	if err != nil {
1763		log.Print(err)
1764	}
1765}
1766
1767func savehfcs(w http.ResponseWriter, r *http.Request) {
1768	userinfo := login.GetUserInfo(r)
1769	itsok := r.FormValue("itsok")
1770	if itsok == "iforgiveyou" {
1771		hfcsid, _ := strconv.ParseInt(r.FormValue("hfcsid"), 10, 0)
1772		_, err := stmtDeleteFilter.Exec(userinfo.UserID, hfcsid)
1773		if err != nil {
1774			log.Printf("error deleting filter: %s", err)
1775		}
1776		filtcache.Clear(userinfo.UserID)
1777		http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1778		return
1779	}
1780
1781	filt := new(Filter)
1782	filt.Name = strings.TrimSpace(r.FormValue("name"))
1783	filt.Date = time.Now().UTC()
1784	filt.Actor = strings.TrimSpace(r.FormValue("actor"))
1785	filt.IncludeAudience = r.FormValue("incaud") == "yes"
1786	filt.Text = strings.TrimSpace(r.FormValue("filttext"))
1787	filt.IsAnnounce = r.FormValue("isannounce") == "yes"
1788	filt.AnnounceOf = strings.TrimSpace(r.FormValue("announceof"))
1789	filt.Reject = r.FormValue("doreject") == "yes"
1790	filt.SkipMedia = r.FormValue("doskipmedia") == "yes"
1791	filt.Hide = r.FormValue("dohide") == "yes"
1792	filt.Collapse = r.FormValue("docollapse") == "yes"
1793	filt.Rewrite = strings.TrimSpace(r.FormValue("filtrewrite"))
1794	filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))
1795	if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
1796		filt.Expiration = time.Now().UTC().Add(dur)
1797	}
1798
1799	if filt.Actor == "" && filt.Text == "" {
1800		log.Printf("blank filter")
1801		return
1802	}
1803
1804	j, err := jsonify(filt)
1805	if err == nil {
1806		_, err = stmtSaveFilter.Exec(userinfo.UserID, j)
1807	}
1808	if err != nil {
1809		log.Printf("error saving filter: %s", err)
1810	}
1811
1812	filtcache.Clear(userinfo.UserID)
1813	http.Redirect(w, r, "/hfcs", http.StatusSeeOther)
1814}
1815
1816func accountpage(w http.ResponseWriter, r *http.Request) {
1817	u := login.GetUserInfo(r)
1818	user, _ := butwhatabout(u.Username)
1819	templinfo := getInfo(r)
1820	templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
1821	templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
1822	templinfo["User"] = user
1823	err := readviews.Execute(w, "account.html", templinfo)
1824	if err != nil {
1825		log.Print(err)
1826	}
1827}
1828
1829func dochpass(w http.ResponseWriter, r *http.Request) {
1830	err := login.ChangePassword(w, r)
1831	if err != nil {
1832		log.Printf("error changing password: %s", err)
1833	}
1834	http.Redirect(w, r, "/account", http.StatusSeeOther)
1835}
1836
1837func fingerlicker(w http.ResponseWriter, r *http.Request) {
1838	orig := r.FormValue("resource")
1839
1840	log.Printf("finger lick: %s", orig)
1841
1842	if strings.HasPrefix(orig, "acct:") {
1843		orig = orig[5:]
1844	}
1845
1846	name := orig
1847	idx := strings.LastIndexByte(name, '/')
1848	if idx != -1 {
1849		name = name[idx+1:]
1850		if fmt.Sprintf("https://%s/%s/%s", serverName, userSep, name) != orig {
1851			log.Printf("foreign request rejected")
1852			name = ""
1853		}
1854	} else {
1855		idx = strings.IndexByte(name, '@')
1856		if idx != -1 {
1857			name = name[:idx]
1858			if name+"@"+serverName != orig {
1859				log.Printf("foreign request rejected")
1860				name = ""
1861			}
1862		}
1863	}
1864	user, err := butwhatabout(name)
1865	if err != nil {
1866		http.NotFound(w, r)
1867		return
1868	}
1869	if stealthmode(user.ID, r) {
1870		http.NotFound(w, r)
1871		return
1872	}
1873
1874	j := junk.New()
1875	j["subject"] = fmt.Sprintf("acct:%s@%s", user.Name, serverName)
1876	j["aliases"] = []string{user.URL}
1877	l := junk.New()
1878	l["rel"] = "self"
1879	l["type"] = `application/activity+json`
1880	l["href"] = user.URL
1881	j["links"] = []junk.Junk{l}
1882
1883	w.Header().Set("Content-Type", "application/jrd+json")
1884	j.Write(w)
1885}
1886
1887func somedays() string {
1888	secs := 432000 + notrand.Int63n(432000)
1889	return fmt.Sprintf("%d", secs)
1890}
1891
1892func avatate(w http.ResponseWriter, r *http.Request) {
1893	n := r.FormValue("a")
1894	a := avatar(n)
1895	w.Header().Set("Cache-Control", "max-age="+somedays())
1896	w.Write(a)
1897}
1898
1899func serveasset(w http.ResponseWriter, r *http.Request) {
1900	w.Header().Set("Cache-Control", "max-age=7776000")
1901	dir := viewDir
1902	if r.URL.Path == "/local.css" {
1903		dir = dataDir
1904	}
1905	http.ServeFile(w, r, dir+"/views"+r.URL.Path)
1906}
1907func servehelp(w http.ResponseWriter, r *http.Request) {
1908	name := mux.Vars(r)["name"]
1909	w.Header().Set("Cache-Control", "max-age=3600")
1910	http.ServeFile(w, r, viewDir+"/docs/"+name)
1911}
1912func servehtml(w http.ResponseWriter, r *http.Request) {
1913	u := login.GetUserInfo(r)
1914	templinfo := getInfo(r)
1915	templinfo["AboutMsg"] = aboutMsg
1916	templinfo["LoginMsg"] = loginMsg
1917	templinfo["HonkVersion"] = softwareVersion
1918	if r.URL.Path == "/about" {
1919		templinfo["Sensors"] = getSensors()
1920	}
1921	if u == nil {
1922		w.Header().Set("Cache-Control", "max-age=60")
1923	}
1924	err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)
1925	if err != nil {
1926		log.Print(err)
1927	}
1928}
1929func serveemu(w http.ResponseWriter, r *http.Request) {
1930	xid := mux.Vars(r)["xid"]
1931	w.Header().Set("Cache-Control", "max-age="+somedays())
1932	http.ServeFile(w, r, dataDir+"/emus/"+xid)
1933}
1934func servememe(w http.ResponseWriter, r *http.Request) {
1935	xid := mux.Vars(r)["xid"]
1936	w.Header().Set("Cache-Control", "max-age="+somedays())
1937	http.ServeFile(w, r, dataDir+"/memes/"+xid)
1938}
1939
1940func servefile(w http.ResponseWriter, r *http.Request) {
1941	xid := mux.Vars(r)["xid"]
1942	row := stmtGetFileData.QueryRow(xid)
1943	var media string
1944	var data []byte
1945	err := row.Scan(&media, &data)
1946	if err != nil {
1947		log.Printf("error loading file: %s", err)
1948		http.NotFound(w, r)
1949		return
1950	}
1951	w.Header().Set("Content-Type", media)
1952	w.Header().Set("X-Content-Type-Options", "nosniff")
1953	w.Header().Set("Cache-Control", "max-age="+somedays())
1954	w.Write(data)
1955}
1956
1957func nomoroboto(w http.ResponseWriter, r *http.Request) {
1958	io.WriteString(w, "User-agent: *\n")
1959	io.WriteString(w, "Disallow: /a\n")
1960	io.WriteString(w, "Disallow: /d\n")
1961	io.WriteString(w, "Disallow: /meme\n")
1962	io.WriteString(w, "Disallow: /o\n")
1963	for _, u := range allusers() {
1964		fmt.Fprintf(w, "Disallow: /%s/%s/%s/\n", userSep, u.Username, honkSep)
1965	}
1966}
1967
1968func webhydra(w http.ResponseWriter, r *http.Request) {
1969	u := login.GetUserInfo(r)
1970	userid := u.UserID
1971	templinfo := getInfo(r)
1972	templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
1973	page := r.FormValue("page")
1974
1975	wanted, _ := strconv.ParseInt(r.FormValue("tophid"), 10, 0)
1976
1977	var honks []*Honk
1978	switch page {
1979	case "atme":
1980		honks = gethonksforme(userid, wanted)
1981		honks = osmosis(honks, userid, false)
1982		templinfo["ServerMessage"] = "at me!"
1983	case "home":
1984		honks = gethonksforuser(userid, wanted)
1985		honks = osmosis(honks, userid, true)
1986		templinfo["ServerMessage"] = serverMsg
1987	case "first":
1988		honks = gethonksforuserfirstclass(userid, wanted)
1989		honks = osmosis(honks, userid, true)
1990		templinfo["ServerMessage"] = "first class only"
1991	case "saved":
1992		honks = getsavedhonks(userid, wanted)
1993		templinfo["PageName"] = "saved"
1994		templinfo["ServerMessage"] = "saved honks"
1995	case "combo":
1996		c := r.FormValue("c")
1997		honks = gethonksbycombo(userid, c, wanted)
1998		honks = osmosis(honks, userid, false)
1999		templinfo["ServerMessage"] = "honks by combo: " + c
2000	case "convoy":
2001		c := r.FormValue("c")
2002		honks = gethonksbyconvoy(userid, c, wanted)
2003		honks = osmosis(honks, userid, false)
2004		templinfo["ServerMessage"] = "honks in convoy: " + c
2005	case "honker":
2006		xid := r.FormValue("xid")
2007		if strings.IndexByte(xid, '@') != -1 {
2008			xid = gofish(xid)
2009		}
2010		honks = gethonksbyxonker(userid, xid, wanted)
2011		msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
2012		templinfo["ServerMessage"] = msg
2013	default:
2014		http.NotFound(w, r)
2015	}
2016	if len(honks) > 0 {
2017		templinfo["TopHID"] = honks[0].ID
2018	} else {
2019		templinfo["TopHID"] = wanted
2020	}
2021	reverbolate(userid, honks)
2022	templinfo["Honks"] = honks
2023	w.Header().Set("Content-Type", "text/html; charset=utf-8")
2024	err := readviews.Execute(w, "honkfrags.html", templinfo)
2025	if err != nil {
2026		log.Printf("frag error: %s", err)
2027	}
2028}
2029
2030func apihandler(w http.ResponseWriter, r *http.Request) {
2031	u := login.GetUserInfo(r)
2032	action := r.FormValue("action")
2033	log.Printf("api request '%s' on behalf of %s", action, u.Username)
2034	switch action {
2035	case "honk":
2036		submithonk(w, r)
2037	default:
2038		http.Error(w, "unknown action", http.StatusNotFound)
2039	}
2040}
2041
2042var endoftheworld = make(chan bool)
2043var readyalready = make(chan bool)
2044
2045func enditall() {
2046	sig := make(chan os.Signal)
2047	signal.Notify(sig, os.Interrupt)
2048	<-sig
2049	count := 0
2050	log.Printf("stopping...")
2051sendloop:
2052	for {
2053		select {
2054		case endoftheworld <- true:
2055			count++
2056		default:
2057			break sendloop
2058		}
2059	}
2060	log.Printf("waiting...")
2061	for i := 0; i < count; i++ {
2062		<-readyalready
2063	}
2064	log.Printf("apocalypse")
2065	os.Exit(0)
2066}
2067
2068func serve() {
2069	db := opendatabase()
2070	login.Init(db)
2071
2072	listener, err := openListener()
2073	if err != nil {
2074		log.Fatal(err)
2075	}
2076	startBackendServer()
2077	go enditall()
2078	go redeliverator()
2079	go tracker()
2080
2081	debug := false
2082	getconfig("debug", &debug)
2083	readviews = templates.Load(debug,
2084		viewDir+"/views/honkpage.html",
2085		viewDir+"/views/honkfrags.html",
2086		viewDir+"/views/honkers.html",
2087		viewDir+"/views/hfcs.html",
2088		viewDir+"/views/combos.html",
2089		viewDir+"/views/honkform.html",
2090		viewDir+"/views/honk.html",
2091		viewDir+"/views/account.html",
2092		viewDir+"/views/about.html",
2093		viewDir+"/views/funzone.html",
2094		viewDir+"/views/login.html",
2095		viewDir+"/views/xzone.html",
2096		viewDir+"/views/msg.html",
2097		viewDir+"/views/header.html",
2098		viewDir+"/views/onts.html",
2099		viewDir+"/views/honkpage.js",
2100	)
2101	if !debug {
2102		assets := []string{viewDir + "/views/style.css", dataDir + "/views/local.css", viewDir + "/views/honkpage.js"}
2103		for _, s := range assets {
2104			savedassetparams[s] = getassetparam(s)
2105		}
2106	}
2107
2108	mux := mux.NewRouter()
2109	mux.Use(login.Checker)
2110
2111	mux.Handle("/api", login.TokenRequired(http.HandlerFunc(apihandler)))
2112
2113	posters := mux.Methods("POST").Subrouter()
2114	getters := mux.Methods("GET").Subrouter()
2115
2116	getters.HandleFunc("/", homepage)
2117	getters.HandleFunc("/home", homepage)
2118	getters.HandleFunc("/front", homepage)
2119	getters.HandleFunc("/events", homepage)
2120	getters.HandleFunc("/robots.txt", nomoroboto)
2121	getters.HandleFunc("/rss", showrss)
2122	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}", showuser)
2123	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/"+honkSep+"/{xid:[[:alnum:]]+}", showonehonk)
2124	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/rss", showrss)
2125	posters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/inbox", inbox)
2126	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/outbox", outbox)
2127	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/followers", emptiness)
2128	getters.HandleFunc("/"+userSep+"/{name:[[:alnum:]]+}/following", emptiness)
2129	getters.HandleFunc("/a", avatate)
2130	getters.HandleFunc("/o", thelistingoftheontologies)
2131	getters.HandleFunc("/o/{name:.+}", showontology)
2132	getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
2133	getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu)
2134	getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe)
2135	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
2136
2137	getters.HandleFunc("/server", serveractor)
2138	posters.HandleFunc("/server/inbox", serverinbox)
2139
2140	getters.HandleFunc("/style.css", serveasset)
2141	getters.HandleFunc("/local.css", serveasset)
2142	getters.HandleFunc("/honkpage.js", serveasset)
2143	getters.HandleFunc("/about", servehtml)
2144	getters.HandleFunc("/login", servehtml)
2145	posters.HandleFunc("/dologin", login.LoginFunc)
2146	getters.HandleFunc("/logout", login.LogoutFunc)
2147	getters.HandleFunc("/help/{name:[[:alnum:]_.-]+}", servehelp)
2148
2149	loggedin := mux.NewRoute().Subrouter()
2150	loggedin.Use(login.Required)
2151	loggedin.HandleFunc("/first", homepage)
2152	loggedin.HandleFunc("/saved", homepage)
2153	loggedin.HandleFunc("/account", accountpage)
2154	loggedin.HandleFunc("/funzone", showfunzone)
2155	loggedin.HandleFunc("/chpass", dochpass)
2156	loggedin.HandleFunc("/atme", homepage)
2157	loggedin.HandleFunc("/hfcs", hfcspage)
2158	loggedin.HandleFunc("/xzone", xzone)
2159	loggedin.HandleFunc("/newhonk", newhonkpage)
2160	loggedin.HandleFunc("/edit", edithonkpage)
2161	loggedin.Handle("/honk", login.CSRFWrap("honkhonk", http.HandlerFunc(submithonk)))
2162	loggedin.Handle("/bonk", login.CSRFWrap("honkhonk", http.HandlerFunc(submitbonk)))
2163	loggedin.Handle("/zonkit", login.CSRFWrap("honkhonk", http.HandlerFunc(zonkit)))
2164	loggedin.Handle("/savehfcs", login.CSRFWrap("filter", http.HandlerFunc(savehfcs)))
2165	loggedin.Handle("/saveuser", login.CSRFWrap("saveuser", http.HandlerFunc(saveuser)))
2166	loggedin.Handle("/ximport", login.CSRFWrap("ximport", http.HandlerFunc(ximport)))
2167	loggedin.HandleFunc("/honkers", showhonkers)
2168	loggedin.HandleFunc("/h/{name:[[:alnum:]_.-]+}", showhonker)
2169	loggedin.HandleFunc("/h", showhonker)
2170	loggedin.HandleFunc("/c/{name:[[:alnum:]_.-]+}", showcombo)
2171	loggedin.HandleFunc("/c", showcombos)
2172	loggedin.HandleFunc("/t", showconvoy)
2173	loggedin.HandleFunc("/q", showsearch)
2174	loggedin.HandleFunc("/hydra", webhydra)
2175	loggedin.Handle("/submithonker", login.CSRFWrap("submithonker", http.HandlerFunc(submithonker)))
2176
2177	err = http.Serve(listener, mux)
2178	if err != nil {
2179		log.Fatal(err)
2180	}
2181}