Home Download Docs Code Community
     1	/*
     2	Copyright 2015 The Perkeep Authors.
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    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	// HelpHandler publishes information related to accessing the server
    68	type HelpHandler struct {
    69		clientConfig  *clientconfig.Config // generated from serverConfig
    70		serverConfig  jsonconfig.Obj       // low-level config
    71		goTemplate    *template.Template   // for rendering
    72		serverSecRing string
    73	}
    74	
    75	// SetServerConfig enables the handler to receive the server config
    76	// before InitHandler, which generates a client config from the server config, is called.
    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	// fixServerInConfig checks if cc contains a meaningful server (for a client).
    88	// If not, a newly allocated clone of cc is returned, except req.Host is used for
    89	// the hostname of the server. Otherwise, cc is returned.
    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	}
Website layout inspired by memcached.
Content by the authors.