all repos — honk @ c89d4c002fdd483fd82ea8dd63988f48d0d958e3

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