123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- package main
- import (
- "notabug.org/apiote/amuse/accounts"
- "notabug.org/apiote/amuse/db"
- "notabug.org/apiote/amuse/front"
- "notabug.org/apiote/amuse/libamuse"
- "notabug.org/apiote/amuse/network"
- "notabug.org/apiote/amuse/config"
- "crypto/sha256"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "mime"
- "net/http"
- "net/url"
- "os"
- "strconv"
- "strings"
- "time"
- )
- func person(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- etag := r.Header.Get("Etag")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- _, err := strconv.ParseInt(path[1], 10, 64)
- if err != nil {
- renderError(400, w, err, acceptLanguages, mimetype)
- return
- } else if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- person, err := libamuse.ShowPerson(path[1], etag, acceptLanguages, mimetype, auth)
- render(person, err, w, acceptLanguages, mimetype)
- }
- func tvSerie(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- etag := r.Header.Get("Etag")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- _, err := strconv.ParseInt(path[1], 10, 64)
- if err != nil {
- renderError(400, w, err, acceptLanguages, mimetype)
- return
- } else if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- tvSerie, err := libamuse.ShowTvSerie(path[1], etag, acceptLanguages, mimetype, auth)
- render(tvSerie, err, w, acceptLanguages, mimetype)
- }
- func film(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- _, err := strconv.ParseInt(path[1], 10, 64)
- if err != nil {
- renderError(400, w, err, acceptLanguages, mimetype)
- return
- } else if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- film, err := libamuse.ShowFilm(path[1], acceptLanguages, mimetype, auth)
- render(film, err, w, acceptLanguages, mimetype)
- }
- func book(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- book, err := libamuse.ShowBook(path[1], acceptLanguages, mimetype, auth)
- render(book, err, w, acceptLanguages, mimetype)
- }
- func bookSerie(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- bookSerie, err := libamuse.ShowBookSerie(path[1], acceptLanguages, mimetype, auth)
- render(bookSerie, err, w, acceptLanguages, mimetype)
- }
- func search(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- if len(path) > 2 {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- query := r.URL.Query().Get("q")
- page := r.URL.Query().Get("page")
- results, err := libamuse.PerformSearch(query, acceptLanguages, mimetype, page, auth)
- render(results, err, w, acceptLanguages, mimetype)
- }
- func index(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- path := strings.Split(r.URL.Path[1:], "/")
- if path[0] != "" {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- index, err := libamuse.ShowIndex(acceptLanguages, mimetype, auth)
- render(index, err, w, acceptLanguages, mimetype)
- }
- func about(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- about, err := libamuse.ShowAbout(acceptLanguages, mimetype, auth)
- render(about, err, w, acceptLanguages, mimetype)
- }
- func loginGet(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) {
- referer := r.Header.Get("Referer")
- target := getTarget(referer, r.Host)
- if target == "/signedup" {
- target = "/"
- }
- auth := getAuthToken(r)
- user, _ := libamuse.VerifyAuthToken(auth)
- if !user.IsEmpty() {
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- return
- }
- login, err := libamuse.ShowLogin(acceptLanguages, mimetype, nil, target)
- render(login, err, w, acceptLanguages, mimetype)
- }
- func loginPost(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) {
- // todo check mimetype (html,capnproto)
- r.ParseForm()
- username := r.PostForm.Get("username")
- password := r.PostForm.Get("password")
- target := r.PostForm.Get("target")
- if target == "" {
- target = "/"
- }
- sfa := r.PostForm.Get("sfa")
- remember := r.PostForm.Get("remember") == "true"
- token, err := libamuse.DoLogin(username, password, sfa, remember)
- if err != nil {
- fmt.Println(err)
- if authErr, ok := err.(accounts.AuthError); ok {
- var login string
- var err error
- if mimetype == "text/html" {
- login, err = libamuse.ShowLogin(acceptLanguages, mimetype, &authErr, target)
- } else {
- // todo send capnproto not authed
- }
- render(login, err, w, acceptLanguages, mimetype)
- } else {
- render("", err, w, acceptLanguages, mimetype)
- }
- } else {
- if mimetype == "text/html" {
- setAuthCookie(remember, token, w)
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- } else {
- // todo send capnproto authed
- }
- }
- }
- func login(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- defer recovery(acceptLanguages, mimetype, w)
- if r.Method == "" || r.Method == "GET" {
- loginGet(w, r, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- loginPost(w, r, acceptLanguages, mimetype)
- }
- }
- func signupGet(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) {
- auth := getAuthToken(r)
- user, _ := libamuse.VerifyAuthToken(auth)
- host := r.Host
- if !user.IsEmpty() {
- w.Header().Add("Location", "/")
- w.WriteHeader(303)
- return
- }
- signup, err := libamuse.ShowSignup(acceptLanguages, mimetype, nil, false, "", "", host)
- render(signup, err, w, acceptLanguages, mimetype)
- }
- func signupPost(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) {
- // todo check mimetype (html,capnproto)
- if !config.OpenRegistration {
- err := errors.New("423")
- render("", err, w, acceptLanguages, mimetype)
- return
- }
- r.ParseForm()
- username := r.PostForm.Get("username")
- password := r.PostForm.Get("password")
- passwordConfirm := r.PostForm.Get("password2")
- sfaEnabled := r.PostForm.Get("sfaEnabled") == "true"
- sfaSecret := r.PostForm.Get("sfaSecret")
- sfa := r.PostForm.Get("sfa")
- host := r.Host
- recoveryCodes, err := libamuse.DoSignup(username, password, passwordConfirm, sfaEnabled, sfaSecret, sfa)
- if err != nil {
- fmt.Println(err)
- if authErr, ok := err.(accounts.AuthError); ok {
- var signup string
- var err error
- if mimetype == "text/html" {
- signup, err = libamuse.ShowSignup(acceptLanguages, mimetype, &authErr, sfaEnabled, sfaSecret, username, host)
- } else {
- // todo send capnproto not authed
- }
- render(signup, err, w, acceptLanguages, mimetype)
- } else {
- render("", err, w, acceptLanguages, mimetype)
- }
- } else {
- if mimetype == "text/html" {
- w.Header().Add("Location", "/signedup?recoveryCodes="+recoveryCodes)
- w.WriteHeader(303)
- } else {
- // todo send capnproto authed
- }
- }
- }
- func signup(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- defer recovery(acceptLanguages, mimetype, w)
- if r.Method == "" || r.Method == "GET" {
- signupGet(w, r, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- signupPost(w, r, acceptLanguages, mimetype)
- }
- }
- func signedup(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- recoveryCodes := r.URL.Query().Get("recoveryCodes")
- defer recovery(acceptLanguages, mimetype, w)
- signedup, err := libamuse.ShowSignedup(acceptLanguages, mimetype, recoveryCodes)
- render(signedup, err, w, acceptLanguages, mimetype)
- }
- func static(w http.ResponseWriter, r *http.Request) {
- etagReq := r.Header.Get("If-None-Match")
- f, err := os.Open(config.DataHome + "/" + r.URL.Path[1:])
- if err != nil {
- w.WriteHeader(500)
- return
- }
- defer f.Close()
- h := sha256.New()
- if _, err := io.Copy(h, f); err != nil {
- w.WriteHeader(500)
- return
- }
- etag := base64.StdEncoding.EncodeToString(h.Sum(nil))
- mime.AddExtensionType(".woff2", "font/woff2")
- w.Header().Set("ETag", etag)
- if etagReq == etag {
- w.WriteHeader(304)
- } else {
- s := strings.Split(f.Name(), ".")
- ext := "." + s[len(s)-1]
- mimetype := mime.TypeByExtension(ext)
- w.Header().Set("Content-Type", mimetype+"; charset=utf-8")
- f.Seek(0, 0)
- if _, err := io.Copy(w, f); err != nil {
- w.WriteHeader(500)
- }
- }
- }
- func user(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages, mimetype string) {
- // todo user profile
- renderError(404, w, nil, acceptLanguages, mimetype)
- }
- func userAvatar(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
- etagReq := r.Header.Get("If-None-Match")
- r.ParseForm()
- size := r.Form.Get("size")
- avatar, err := libamuse.ShowUserAvatar(username, etagReq, auth, size == "small")
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- }
- if string(avatar.Data) == "" {
- w.WriteHeader(304)
- return
- }
- w.Header().Set("Content-Type", avatar.Mimetype)
- w.Header().Set("ETag", avatar.Etag)
- w.Write(avatar.Data)
- }
- func addToWantlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages, mimetype string) {
- r.ParseForm()
- itemId := r.PostForm.Get("itemId")
- itemType := r.PostForm.Get("itemType")
- target := "/" + itemType + "s/" + itemId
- err := libamuse.AddToWantlist(username, auth, itemId, itemType, acceptLanguages, mimetype)
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- } else {
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- }
- }
- func userWatchlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
- if r.Method == "" || r.Method == "GET" {
- var page int
- r.ParseForm()
- filter := r.Form.Get("filter")
- fmt.Sscanf(r.Form.Get("page"), "%d", &page)
- watchlist, err := libamuse.ShowWatchlist(username, auth, acceptLanguages, mimetype, filter, page)
- render(watchlist, err, w, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- addToWantlist(w, r, username, auth, acceptLanguages, mimetype)
- }
- }
- func userTvQueue(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
- if r.Method == "" || r.Method == "GET" {
- var page int
- r.ParseForm()
- filter := r.Form.Get("filter")
- fmt.Sscanf(r.Form.Get("page"), "%d", &page)
- tvQueue, err := libamuse.ShowTvQueue(username, auth, acceptLanguages, mimetype, filter, page)
- render(tvQueue, err, w, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- r.ParseForm()
- itemId := r.PostForm.Get("itemId")
- itemType := r.PostForm.Get("itemType")
- masterItemId := strings.Split(itemId, "/")[0]
- target := "/" + itemType + "s/" + masterItemId
- err := libamuse.AddToWantlist(username, auth, itemId, itemType, acceptLanguages, mimetype)
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- } else {
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- }
- }
- }
- func userReadlist(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
- if r.Method == "" || r.Method == "GET" {
- var page int
- r.ParseForm()
- filter := r.Form.Get("filter")
- fmt.Sscanf(r.Form.Get("page"), "%d", &page)
- readlist, err := libamuse.ShowReadlist(username, auth, acceptLanguages, mimetype, filter, page)
- render(readlist, err, w, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- addToWantlist(w, r, username, auth, acceptLanguages, mimetype)
- }
- }
- func userExperiences(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
- if r.Method == "" || r.Method == "GET" {
- var page int
- r.ParseForm()
- filter := r.Form.Get("filter")
- fmt.Sscanf(r.Form.Get("page"), "%d", &page)
- experiences, err := libamuse.ShowExperiences(username, auth, acceptLanguages, mimetype, filter, page)
- render(experiences, err, w, acceptLanguages, mimetype)
- } else if r.Method == "POST" {
- r.ParseForm()
- itemId := r.PostForm.Get("itemId")
- itemType := r.PostForm.Get("itemType")
- isOtherTime := r.PostForm.Get("isOtherTime") == "true"
- var datetime string
- if isOtherTime {
- date := r.PostForm.Get("experiencedDate")
- time := r.PostForm.Get("experiencedTime")
- datetime = date + "T" + time + ":00"
- } else {
- datetime = ""
- }
- masterItemId := strings.Split(itemId, "/")[0]
- target := "/" + itemType + "s/" + masterItemId
- err := libamuse.AddToExperiences(username, auth, itemId, itemType, datetime, acceptLanguages, mimetype)
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- } else {
- w.Header().Add("Location", target)
- w.WriteHeader(303)
- }
- }
- }
- func sessionDelete(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, session, acceptLanguages, mimetype string) {
- err := libamuse.SessionDelete(username, auth, session, acceptLanguages, mimetype)
- if err != nil {
- render("", err, w, acceptLanguages, mimetype)
- } else {
- w.Header().Add("Location", "/loggedout")
- w.WriteHeader(303)
- }
- }
- func userSessions(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages, mimetype string) {
- path := strings.Split(r.URL.Path[1:], "/")
- if len(path) == 3 {
- // todo show sessions
- renderError(404, w, nil, acceptLanguages, mimetype)
- } else if len(path) == 4 {
- if r.Method == "POST" {
- r.ParseForm()
- method := r.PostForm.Get("method")
- session := path[3]
- if method == "DELETE" {
- sessionDelete(w, r, username, auth, session, acceptLanguages, mimetype)
- }
- } else if r.Method == "DELETE" {
- session := path[3]
- sessionDelete(w, r, username, auth, session, acceptLanguages, mimetype)
- }
- }
- }
- func userRouter(w http.ResponseWriter, r *http.Request) {
- path := strings.Split(r.URL.Path[1:], "/")
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- auth := getAuthToken(r)
- defer recovery(acceptLanguages, mimetype, w)
- if path[1] == "" {
- renderError(404, w, nil, acceptLanguages, mimetype)
- return
- }
- username := path[1]
- if len(path) == 2 {
- user(w, r, username, auth, acceptLanguages, mimetype)
- } else {
- switch path[2] {
- case "avatar":
- userAvatar(w, r, username, auth, acceptLanguages, mimetype)
- case "watchlist":
- userWatchlist(w, r, username, auth, acceptLanguages, mimetype)
- case "tvqueue":
- userTvQueue(w, r, username, auth, acceptLanguages, mimetype)
- case "readlist":
- userReadlist(w, r, username, auth, acceptLanguages, mimetype)
- case "experiences":
- userExperiences(w, r, username, auth, acceptLanguages, mimetype)
- case "sessions":
- userSessions(w, r, username, auth, acceptLanguages, mimetype)
- default:
- renderError(404, w, nil, acceptLanguages, mimetype)
- }
- }
- }
- func loggedout(w http.ResponseWriter, r *http.Request) {
- acceptLanguages := r.Header.Get("Accept-Language")
- mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
- defer recovery(acceptLanguages, mimetype, w)
- loggedout, err := libamuse.ShowLoggedOut(acceptLanguages, mimetype)
- setAuthCookie(false, "", w)
- render(loggedout, err, w, acceptLanguages, mimetype)
- }
- func route(port uint) {
- address := fmt.Sprintf("%s:%d", config.Address, port)
- http.HandleFunc("/", index)
- http.HandleFunc("/static/", static)
- http.HandleFunc("/about", about)
- http.HandleFunc("/items/", search)
- http.HandleFunc("/films/", film)
- http.HandleFunc("/tvseries/", tvSerie)
- http.HandleFunc("/persons/", person)
- http.HandleFunc("/books/", book)
- http.HandleFunc("/bookseries/", bookSerie)
- http.HandleFunc("/users/", userRouter)
- http.HandleFunc("/login", login)
- http.HandleFunc("/signup", signup)
- http.HandleFunc("/signedup", signedup)
- http.HandleFunc("/loggedout", loggedout)
- fmt.Printf("running on %s\n", address)
- e := http.ListenAndServe(address, nil)
- if e != nil {
- fmt.Println(e)
- }
- }
- func getTarget(referer, host string) string {
- url, err := url.Parse(referer)
- if err != nil {
- fmt.Println(err)
- return "/"
- }
- target := url.EscapedPath()
- if target == "" || url.Host != host {
- target = "/"
- }
- return target
- }
- func setAuthCookie(remember bool, token string, w http.ResponseWriter) {
- cookie := http.Cookie{
- Name: "auth", Value: token, HttpOnly: true,
- //SameSite: http.SameSiteStrictMode, Secure: true, // note turn on in prod (https)
- }
- if remember {
- cookie.Expires = time.Now().Add(1000000000 * 60 * 60 * 24 * 30)
- } else {
- cookie.Expires = time.Now().Add(1000000000 * 60 * 60 * 24)
- }
- http.SetCookie(w, &cookie)
- }
- func getAuthToken(r *http.Request) accounts.Authentication {
- cookie, err := r.Cookie("auth")
- if err == nil {
- return accounts.Authentication{
- Token: cookie.Value,
- }
- }
- return accounts.Authentication{
- Token: r.Header.Get("Authorization"),
- }
- }
- func recovery(languages, mimetype string, w http.ResponseWriter) {
- if r := recover(); r != nil {
- renderError(500, w, errors.New(r.(string)), languages, mimetype)
- }
- }
- func render(result string, e error, w http.ResponseWriter, languages, mimetype string) {
- if e != nil {
- fmt.Println(e)
- if _, ok := e.(front.NoSuchRendererError); ok {
- renderError(406, w, e, languages, mimetype)
- } else if httpError, ok := e.(network.HttpError); ok {
- renderError(httpError.Status, w, httpError, languages, mimetype)
- } else if _, ok := e.(db.EmptyError); ok {
- renderError(410, w, e, languages, mimetype)
- } else if authError, ok := e.(accounts.AuthError); ok {
- if authError.Err.Error() == "401" {
- w.Header().Add("WWW-Authenticate", "Bearer")
- renderError(401, w, e, languages, mimetype)
- } else {
- renderError(403, w, e, languages, mimetype)
- }
- } else if e.Error() == "423" {
- renderError(423, w, e, languages, mimetype)
- } else {
- renderError(500, w, e, languages, mimetype)
- }
- } else {
- fmt.Fprint(w, result)
- }
- }
- func renderError(code int, w http.ResponseWriter, e error, languages, mimetype string) {
- w.WriteHeader(code)
- if code != 406 {
- errorPage, err := libamuse.ShowErrorPage(code, languages, mimetype)
- if err != nil {
- fmt.Fprintf(w, "Fatal error while rendering error %d.\nContact admin.", code)
- }
- fmt.Fprint(w, errorPage)
- }
- }
|