1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package server
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "html/template"
24 "net/http"
25 "strconv"
26 "strings"
27
28 "go4.org/jsonconfig"
29 "perkeep.org/internal/httputil"
30 "perkeep.org/pkg/blobserver"
31 "perkeep.org/pkg/types/clientconfig"
32 )
33
34 const helpHTML string = `<html>
35 <head>
36 <title>Help</title>
37 </head>
38 <body>
39 <h2>Help</h2>
40
41 <h3>Web User Interface</h3>
42 <p><a href='https://perkeep.org/doc/search-ui'>Search bar predicates.</a></p>
43
44 <h3>Client tools</h3>
45
46 <p>
47 You can download the Perkeep command line tools for Linux, Mac, and Windows at:
48 <ul>
49 <li><a href="https://perkeep.org/download">perkeep.org/download</a></li>
50 </ul>
51 </p>
52
53 <p>You will need to use the following <a href='https://perkeep.org/doc/client-config'>client configuration</a> in order to access this server using the command line tools.</p>
54 <pre>{{ .ClientConfigJSON }}</pre>
55
56 {{ .SecringDownloadHint }}
57
58 <h3>Anything Else?</h3>
59 <p>See the Perkeep <a href='https://perkeep.org/doc/'>online documentation</a> and <a href='https://perkeep.org/community'>community contacts</a>.</p>
60
61 <h3>Attribution</h3>
62 <p>Various mapping data and services <a href="https://osm.org/copyright">copyright OpenStreetMap contributors</a>, ODbL 1.0.</p>
63
64 </body>
65 </html>`
66
67
68 type HelpHandler struct {
69 clientConfig *clientconfig.Config
70 serverConfig jsonconfig.Obj
71 goTemplate *template.Template
72 serverSecRing string
73 }
74
75
76
77 func (hh *HelpHandler) SetServerConfig(config jsonconfig.Obj) {
78 if hh.serverConfig == nil {
79 hh.serverConfig = config
80 }
81 }
82
83 func init() {
84 blobserver.RegisterHandlerConstructor("help", newHelpFromConfig)
85 }
86
87
88
89
90 func fixServerInConfig(cc *clientconfig.Config, req *http.Request) (*clientconfig.Config, error) {
91 if cc == nil {
92 return nil, errors.New("nil client config")
93 }
94 if len(cc.Servers) == 0 || cc.Servers["default"] == nil || cc.Servers["default"].Server == "" {
95 return nil, errors.New("no Server in client config")
96 }
97 listen := strings.TrimPrefix(strings.TrimPrefix(cc.Servers["default"].Server, "http://"), "https://")
98 if !(strings.HasPrefix(listen, "0.0.0.0") || strings.HasPrefix(listen, ":")) {
99 return cc, nil
100 }
101 newCC := *cc
102 server := newCC.Servers["default"]
103 if req.TLS != nil {
104 server.Server = "https://" + req.Host
105 } else {
106 server.Server = "http://" + req.Host
107 }
108 newCC.Servers["default"] = server
109 return &newCC, nil
110 }
111
112 func (hh *HelpHandler) InitHandler(hl blobserver.FindHandlerByTyper) error {
113 if hh.serverConfig == nil {
114 return fmt.Errorf("HelpHandler's serverConfig must be set before calling its InitHandler")
115 }
116
117 clientConfig, err := clientconfig.GenerateClientConfig(hh.serverConfig)
118 if err != nil {
119 return fmt.Errorf("error generating client config: %v", err)
120 }
121 hh.clientConfig = clientConfig
122
123 hh.serverSecRing = clientConfig.IdentitySecretRing
124 clientConfig.IdentitySecretRing = "/home/you/.config/perkeep/identity-secring.gpg"
125
126 tmpl, err := template.New("help").Parse(helpHTML)
127 if err != nil {
128 return fmt.Errorf("error creating template: %v", err)
129 }
130 hh.goTemplate = tmpl
131
132 return nil
133 }
134
135 func newHelpFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
136 return &HelpHandler{}, nil
137 }
138
139 func (hh *HelpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
140 suffix := httputil.PathSuffix(req)
141 if !httputil.IsGet(req) {
142 http.Error(rw, "Illegal help method.", http.StatusMethodNotAllowed)
143 return
144 }
145 switch suffix {
146 case "":
147 cc, err := fixServerInConfig(hh.clientConfig, req)
148 if err != nil {
149 httputil.ServeError(rw, req, err)
150 return
151 }
152 if clientConfig := req.FormValue("clientConfig"); clientConfig != "" {
153 if clientConfigOnly, err := strconv.ParseBool(clientConfig); err == nil && clientConfigOnly {
154 httputil.ReturnJSON(rw, cc)
155 return
156 }
157 }
158 hh.serveHelpHTML(cc, rw, req)
159 default:
160 http.Error(rw, "Illegal help path.", http.StatusNotFound)
161 }
162 }
163
164 func (hh *HelpHandler) serveHelpHTML(cc *clientconfig.Config, rw http.ResponseWriter, req *http.Request) {
165 jsonBytes, err := json.MarshalIndent(cc, "", " ")
166 if err != nil {
167 httputil.ServeError(rw, req, fmt.Errorf("could not serialize client config JSON: %v", err))
168 return
169 }
170
171 var hint template.HTML
172 if strings.HasPrefix(hh.serverSecRing, "/gcs/") {
173 bucketdir := strings.TrimPrefix(hh.serverSecRing, "/gcs/")
174 bucketdir = strings.TrimSuffix(bucketdir, "/identity-secring.gpg")
175 hint = template.HTML(fmt.Sprintf("<p>Download your GnuPG secret ring from <a href=\"https://console.developers.google.com/storage/browser/%s/\">https://console.developers.google.com/storage/browser/%s/</a> and place it in your <a href='https://perkeep.org/doc/client-config'>Perkeep client config directory</a>. Keep it private. It's not encrypted or password-protected and anybody in possession of it can create Perkeep claims as your identity.</p>\n",
176 bucketdir, bucketdir))
177 }
178
179 hh.goTemplate.Execute(rw, struct {
180 ClientConfigJSON string
181 SecringDownloadHint template.HTML
182 }{
183 ClientConfigJSON: string(jsonBytes),
184 SecringDownloadHint: hint,
185 })
186 }