github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/server/root.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     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  	"fmt"
    22  	"log"
    23  	"net/http"
    24  	"os"
    25  	"sort"
    26  	"sync"
    27  	"time"
    28  
    29  	"camlistore.org/pkg/auth"
    30  	"camlistore.org/pkg/blobserver"
    31  	"camlistore.org/pkg/buildinfo"
    32  	"camlistore.org/pkg/images"
    33  	"camlistore.org/pkg/jsonconfig"
    34  	"camlistore.org/pkg/osutil"
    35  	"camlistore.org/pkg/search"
    36  )
    37  
    38  // RootHandler handles serving the about/splash page.
    39  type RootHandler struct {
    40  	// Stealth determines whether we hide from non-authenticated
    41  	// clients.
    42  	Stealth bool
    43  
    44  	OwnerName string // for display purposes only.
    45  	Username  string // default user for mobile setup.
    46  
    47  	// URL prefixes (path or full URL) to the primary blob and
    48  	// search root.
    49  	BlobRoot   string
    50  	SearchRoot string
    51  	statusRoot string
    52  	Prefix     string // root handler's prefix
    53  
    54  	Storage blobserver.Storage // of BlobRoot, or nil
    55  
    56  	searchInitOnce sync.Once // runs searchInit, which populates searchHandler
    57  	searchInit     func()
    58  	searchHandler  *search.Handler // of SearchRoot, or nil
    59  
    60  	ui   *UIHandler     // or nil, if none configured
    61  	sync []*SyncHandler // list of configured sync handlers, for discovery.
    62  }
    63  
    64  func (rh *RootHandler) SearchHandler() (h *search.Handler, ok bool) {
    65  	rh.searchInitOnce.Do(rh.searchInit)
    66  	return rh.searchHandler, rh.searchHandler != nil
    67  }
    68  
    69  func init() {
    70  	blobserver.RegisterHandlerConstructor("root", newRootFromConfig)
    71  }
    72  
    73  func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
    74  	username, err := getUserName()
    75  	if err != nil {
    76  		return
    77  	}
    78  	root := &RootHandler{
    79  		BlobRoot:   conf.OptionalString("blobRoot", ""),
    80  		SearchRoot: conf.OptionalString("searchRoot", ""),
    81  		OwnerName:  conf.OptionalString("ownerName", username),
    82  		Username:   osutil.Username(),
    83  		Prefix:     ld.MyPrefix(),
    84  	}
    85  	root.Stealth = conf.OptionalBool("stealth", false)
    86  	root.statusRoot = conf.OptionalString("statusRoot", "")
    87  	if err = conf.Validate(); err != nil {
    88  		return
    89  	}
    90  
    91  	if root.BlobRoot != "" {
    92  		bs, err := ld.GetStorage(root.BlobRoot)
    93  		if err != nil {
    94  			return nil, fmt.Errorf("Root handler's blobRoot of %q error: %v", root.BlobRoot, err)
    95  		}
    96  		root.Storage = bs
    97  	}
    98  
    99  	root.searchInit = func() {}
   100  	if root.SearchRoot != "" {
   101  		prefix := root.SearchRoot
   102  		if t := ld.GetHandlerType(prefix); t != "search" {
   103  			if t == "" {
   104  				return nil, fmt.Errorf("root handler's searchRoot of %q is invalid and doesn't refer to a declared handler", prefix)
   105  			}
   106  			return nil, fmt.Errorf("root handler's searchRoot of %q is of type %q, not %q", prefix, t, "search")
   107  		}
   108  		root.searchInit = func() {
   109  			h, err := ld.GetHandler(prefix)
   110  			if err != nil {
   111  				log.Fatalf("Error fetching SearchRoot at %q: %v", prefix, err)
   112  			}
   113  			root.searchHandler = h.(*search.Handler)
   114  			root.searchInit = nil
   115  		}
   116  	}
   117  
   118  	return root, nil
   119  }
   120  
   121  func (rh *RootHandler) registerUIHandler(h *UIHandler) {
   122  	rh.ui = h
   123  }
   124  
   125  func (rh *RootHandler) registerSyncHandler(h *SyncHandler) {
   126  	rh.sync = append(rh.sync, h)
   127  	sort.Sort(byFromTo(rh.sync))
   128  }
   129  
   130  func (rh *RootHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   131  	if wantsDiscovery(req) {
   132  		if auth.Allowed(req, auth.OpDiscovery) {
   133  			rh.serveDiscovery(rw, req)
   134  			return
   135  		}
   136  		if !rh.Stealth {
   137  			http.Error(rw, "Unauthorized", http.StatusUnauthorized)
   138  		}
   139  		return
   140  	}
   141  
   142  	if rh.Stealth {
   143  		return
   144  	}
   145  	if req.URL.Path == "/favicon.ico" {
   146  		serveStaticFile(rw, req, Files, "favicon.ico")
   147  		return
   148  	}
   149  	f := func(p string, a ...interface{}) {
   150  		fmt.Fprintf(rw, p, a...)
   151  	}
   152  	f("<html><body><p>This is camlistored (%s), a "+
   153  		"<a href='http://camlistore.org'>Camlistore</a> server.</p>", buildinfo.Version())
   154  	if auth.IsLocalhost(req) && !isDevServer() {
   155  		f("<p>If you're coming from localhost, configure your Camlistore server at <a href='/setup'>/setup</a>.</p>")
   156  	}
   157  	if rh.ui != nil {
   158  		f("<p>To manage your content, access the <a href='%s'>%s</a>.</p>", rh.ui.prefix, rh.ui.prefix)
   159  	}
   160  	if rh.statusRoot != "" {
   161  		f("<p>To view status, see <a href='%s'>%s</a>", rh.statusRoot, rh.statusRoot)
   162  	}
   163  	fmt.Fprintf(rw, "</body></html>")
   164  }
   165  
   166  func isDevServer() bool {
   167  	return os.Getenv("CAMLI_DEV_CAMLI_ROOT") != ""
   168  }
   169  
   170  type byFromTo []*SyncHandler
   171  
   172  func (b byFromTo) Len() int      { return len(b) }
   173  func (b byFromTo) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   174  func (b byFromTo) Less(i, j int) bool {
   175  	if b[i].fromName < b[j].fromName {
   176  		return true
   177  	}
   178  	return b[i].fromName == b[j].fromName && b[i].toName < b[j].toName
   179  }
   180  
   181  func (rh *RootHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
   182  	m := map[string]interface{}{
   183  		"blobRoot":     rh.BlobRoot,
   184  		"searchRoot":   rh.SearchRoot,
   185  		"ownerName":    rh.OwnerName,
   186  		"username":     rh.Username,
   187  		"statusRoot":   rh.statusRoot,
   188  		"wsAuthToken":  auth.ProcessRandom(),
   189  		"thumbVersion": images.ThumbnailVersion(),
   190  	}
   191  	if gener, ok := rh.Storage.(blobserver.Generationer); ok {
   192  		initTime, gen, err := gener.StorageGeneration()
   193  		if err != nil {
   194  			m["storageGenerationError"] = err.Error()
   195  		} else {
   196  			m["storageInitTime"] = initTime.UTC().Format(time.RFC3339)
   197  			m["storageGeneration"] = gen
   198  		}
   199  	} else {
   200  		log.Printf("Storage type %T is not a blobserver.Generationer; not sending storageGeneration", rh.Storage)
   201  	}
   202  	if rh.ui != nil {
   203  		rh.ui.populateDiscoveryMap(m)
   204  	}
   205  	if len(rh.sync) > 0 {
   206  		var syncHandlers []map[string]interface{}
   207  		for _, sh := range rh.sync {
   208  			syncHandlers = append(syncHandlers, sh.discoveryMap())
   209  		}
   210  		m["syncHandlers"] = syncHandlers
   211  	}
   212  	discoveryHelper(rw, req, m)
   213  }
   214  
   215  func discoveryHelper(rw http.ResponseWriter, req *http.Request, m map[string]interface{}) {
   216  	rw.Header().Set("Content-Type", "text/javascript")
   217  	if cb := req.FormValue("cb"); identOrDotPattern.MatchString(cb) {
   218  		fmt.Fprintf(rw, "%s(", cb)
   219  		defer rw.Write([]byte(");\n"))
   220  	} else if v := req.FormValue("var"); identOrDotPattern.MatchString(v) {
   221  		fmt.Fprintf(rw, "%s = ", v)
   222  		defer rw.Write([]byte(";\n"))
   223  	}
   224  	bytes, _ := json.MarshalIndent(m, "", "  ")
   225  	rw.Write(bytes)
   226  }