config.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //
  2. // Copyright (C) 2017-2019 Marcus Rohrmoser, http://purl.mro.name/ShaarliGo
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. //
  17. package main
  18. import (
  19. "encoding/xml"
  20. "html/template"
  21. "io"
  22. "log"
  23. "net/http"
  24. "path"
  25. "strings"
  26. "time"
  27. "golang.org/x/crypto/bcrypt"
  28. )
  29. func mustParseRFC3339(str string) time.Time {
  30. if ret, err := time.Parse(time.RFC3339, str); err != nil {
  31. panic(err)
  32. } else {
  33. return ret
  34. }
  35. }
  36. func (app *Server) handleSettings() http.HandlerFunc {
  37. return func(w http.ResponseWriter, r *http.Request) {
  38. now := time.Now()
  39. if app.cfg.IsConfigured() && !app.IsLoggedIn(now) {
  40. http.Error(w, "double check failed.", http.StatusInternalServerError)
  41. return
  42. }
  43. if err := r.ParseForm(); err != nil {
  44. http.Error(w, "couldn't parse form: "+err.Error(), http.StatusInternalServerError)
  45. return
  46. }
  47. switch r.Method {
  48. case http.MethodPost:
  49. uid := strings.TrimSpace(r.FormValue("setlogin"))
  50. pwd := strings.TrimSpace(r.FormValue("setpassword"))
  51. title := strings.TrimSpace(r.FormValue("title"))
  52. // https://astaxie.gitbooks.io/build-web-application-with-golang/en/09.5.html
  53. // $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
  54. // original shaarli did $hash = sha1($password.$login.$GLOBALS['salt']);
  55. if pwdBcrypt, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost); err != nil {
  56. http.Error(w, "couldn't crypt pwd: "+err.Error(), http.StatusInternalServerError)
  57. return
  58. } else {
  59. if len(uid) < 1 || len([]rune(pwd)) < 12 {
  60. app.cfg.renderSettingsPage(w, http.StatusBadRequest)
  61. return
  62. }
  63. app.cfg.Title = title
  64. app.cfg.Uid = uid
  65. app.cfg.PwdBcrypt = string(pwdBcrypt)
  66. err = app.cfg.Save()
  67. }
  68. if feed, err := LoadFeed(); err != nil {
  69. http.Error(w, "couldn't load seed feed feeds: "+err.Error(), http.StatusInternalServerError)
  70. return
  71. } else {
  72. feed.XmlBase = Iri(app.url.String())
  73. feed.Id = Id(feed.XmlBase) // expand XmlBase as required by https://validator.w3.org/feed/check.cgi?url=
  74. feed.Title = HumanText{Body: title}
  75. feed.Authors = []Person{{Name: uid}}
  76. feed.Links = []Link{
  77. {Rel: relEdit, Href: path.Join(cgiName, uriPub, uriPosts), Title: "PostURI, maybe better a app:collection https://tools.ietf.org/html/rfc5023#section-8.3.3"},
  78. }
  79. if err := app.SaveFeed(feed); err != nil {
  80. http.Error(w, "couldn't store feed data: "+err.Error(), http.StatusInternalServerError)
  81. return
  82. }
  83. if err := app.PublishFeedsForModifiedEntries(feed, feed.Entries); err != nil {
  84. log.Println("couldn't write feeds: ", err.Error())
  85. http.Error(w, "couldn't write feeds: "+err.Error(), http.StatusInternalServerError)
  86. return
  87. }
  88. app.startSession(w, r, now)
  89. http.Redirect(w, r, path.Join("..", "..", uriPub, uriPosts)+"/", http.StatusFound)
  90. }
  91. case http.MethodGet:
  92. app.cfg.renderSettingsPage(w, http.StatusOK)
  93. default:
  94. http.Error(w, "MethodNotAllowed", http.StatusMethodNotAllowed)
  95. }
  96. }
  97. }
  98. func (cfg Config) renderSettingsPage(w http.ResponseWriter, code int) {
  99. byt, _ := tplSettingsHtmlBytes()
  100. tmpl, err := template.New("settings").Parse(string(byt))
  101. if err == nil {
  102. w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  103. w.WriteHeader(code)
  104. io.WriteString(w, xml.Header+
  105. "<?xml-stylesheet type='text/xsl' href='"+path.Join("..", "..", "assets", cfg.Skin, "config.xslt")+"'?>\n")
  106. io.WriteString(w, `<!--
  107. The html you see here is for compatibility with vanilla shaarli.
  108. The main reason is backward compatibility for e.g. http://app.mro.name/ShaarliOS and
  109. https://github.com/dimtion/Shaarlier as tested via
  110. https://code.mro.name/mro/Shaarli-API-test
  111. -->
  112. `)
  113. err = tmpl.Execute(w, map[string]string{
  114. "skin": cfg.Skin,
  115. "title": cfg.Title,
  116. "setlogin": cfg.Uid,
  117. })
  118. }
  119. if err != nil {
  120. http.Error(w, "couldn't restore assets: "+err.Error(), http.StatusInternalServerError)
  121. }
  122. }