2011-03-13 23:38:32 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package search
|
|
|
|
|
|
|
|
import (
|
2011-07-03 00:16:30 +00:00
|
|
|
"bytes"
|
2011-03-13 23:38:32 +00:00
|
|
|
"fmt"
|
|
|
|
"http"
|
2011-03-14 03:51:58 +00:00
|
|
|
"log"
|
2011-03-14 00:14:48 +00:00
|
|
|
"os"
|
2011-03-14 03:51:58 +00:00
|
|
|
"sort"
|
2011-08-25 13:15:38 +00:00
|
|
|
"strconv"
|
2011-05-30 22:44:25 +00:00
|
|
|
"strings"
|
2011-03-14 02:27:59 +00:00
|
|
|
"sync"
|
2011-03-14 00:14:48 +00:00
|
|
|
"time"
|
2011-05-30 05:52:31 +00:00
|
|
|
|
|
|
|
"camli/blobref"
|
|
|
|
"camli/blobserver"
|
|
|
|
"camli/jsonconfig"
|
|
|
|
"camli/httputil"
|
2011-08-25 15:14:47 +00:00
|
|
|
"url"
|
2011-03-13 23:38:32 +00:00
|
|
|
)
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
const buffered = 32 // arbitrary channel buffer size
|
2011-08-25 13:15:38 +00:00
|
|
|
const maxPermanodes = 50 // arbitrary limit on the number of permanodes fetched
|
2011-07-02 15:15:00 +00:00
|
|
|
|
2011-05-30 05:52:31 +00:00
|
|
|
func init() {
|
|
|
|
blobserver.RegisterHandlerConstructor("search", newHandlerFromConfig)
|
|
|
|
}
|
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
type Handler struct {
|
2011-05-30 05:52:31 +00:00
|
|
|
index Index
|
|
|
|
owner *blobref.BlobRef
|
|
|
|
}
|
|
|
|
|
2011-07-06 18:20:25 +00:00
|
|
|
func NewHandler(index Index, owner *blobref.BlobRef) *Handler {
|
|
|
|
return &Handler{index, owner}
|
|
|
|
}
|
|
|
|
|
2011-05-30 05:52:31 +00:00
|
|
|
func newHandlerFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, os.Error) {
|
|
|
|
indexPrefix := conf.RequiredString("index") // TODO: add optional help tips here?
|
|
|
|
ownerBlobStr := conf.RequiredString("owner")
|
|
|
|
if err := conf.Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
indexHandler, err := ld.GetHandler(indexPrefix)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("search config references unknown handler %q", indexPrefix)
|
2011-03-13 23:38:32 +00:00
|
|
|
}
|
2011-05-30 05:52:31 +00:00
|
|
|
indexer, ok := indexHandler.(Index)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("search config references invalid indexer %q (actually a %T)", indexPrefix, indexHandler)
|
|
|
|
}
|
|
|
|
ownerBlobRef := blobref.Parse(ownerBlobStr)
|
|
|
|
if ownerBlobRef == nil {
|
|
|
|
return nil, fmt.Errorf("search 'owner' has malformed blobref %q; expecting e.g. sha1-xxxxxxxxxxxx",
|
|
|
|
ownerBlobStr)
|
|
|
|
}
|
2011-06-17 03:45:47 +00:00
|
|
|
return &Handler{
|
2011-05-30 05:52:31 +00:00
|
|
|
index: indexer,
|
|
|
|
owner: ownerBlobRef,
|
|
|
|
}, nil
|
2011-03-13 23:38:32 +00:00
|
|
|
}
|
|
|
|
|
2011-06-23 19:11:01 +00:00
|
|
|
// TODO: figure out a plan for an owner having multiple active public keys, or public
|
|
|
|
// key rotation
|
|
|
|
func (h *Handler) Owner() *blobref.BlobRef {
|
|
|
|
return h.owner
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *Handler) Index() Index {
|
|
|
|
return h.index
|
|
|
|
}
|
2011-03-14 00:14:48 +00:00
|
|
|
|
2011-06-09 19:55:38 +00:00
|
|
|
func jsonMap() map[string]interface{} {
|
|
|
|
return make(map[string]interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
func jsonMapList() []map[string]interface{} {
|
|
|
|
return make([]map[string]interface{}, 0)
|
|
|
|
}
|
2011-03-14 02:27:59 +00:00
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2011-06-30 23:16:08 +00:00
|
|
|
ret := jsonMap()
|
2011-05-30 22:44:25 +00:00
|
|
|
_ = req.Header.Get("X-PrefixHandler-PathBase")
|
|
|
|
suffix := req.Header.Get("X-PrefixHandler-PathSuffix")
|
|
|
|
|
2011-06-09 19:55:38 +00:00
|
|
|
if req.Method == "GET" {
|
|
|
|
switch suffix {
|
2011-06-23 04:59:17 +00:00
|
|
|
case "camli/search/recent":
|
2011-06-09 19:55:38 +00:00
|
|
|
sh.serveRecentPermanodes(rw, req)
|
|
|
|
return
|
2011-07-02 15:15:00 +00:00
|
|
|
case "camli/search/tag":
|
|
|
|
sh.serveTaggedPermanodes(rw, req)
|
|
|
|
return
|
2011-08-25 13:15:38 +00:00
|
|
|
case "camli/search/request":
|
|
|
|
sh.serveRequestedPermanodes(rw, req)
|
|
|
|
return
|
2011-06-09 19:55:38 +00:00
|
|
|
case "camli/search/describe":
|
|
|
|
sh.serveDescribe(rw, req)
|
|
|
|
return
|
|
|
|
case "camli/search/claims":
|
|
|
|
sh.serveClaims(rw, req)
|
|
|
|
return
|
|
|
|
case "camli/search/files":
|
|
|
|
sh.serveFiles(rw, req)
|
|
|
|
return
|
2011-06-23 05:21:18 +00:00
|
|
|
case "camli/search/signerattrvalue":
|
|
|
|
sh.serveSignerAttrValue(rw, req)
|
|
|
|
return
|
2011-06-26 00:50:38 +00:00
|
|
|
case "camli/search/signerpaths":
|
|
|
|
sh.serveSignerPaths(rw, req)
|
|
|
|
return
|
2011-06-09 19:55:38 +00:00
|
|
|
}
|
2011-05-30 22:44:25 +00:00
|
|
|
}
|
|
|
|
|
2011-06-09 19:55:38 +00:00
|
|
|
// TODO: discovery for the endpoints & better error message with link to discovery info
|
|
|
|
ret["error"] = "Unsupported search path or method"
|
|
|
|
ret["errorType"] = "input"
|
2011-07-01 18:19:54 +00:00
|
|
|
httputil.ReturnJson(rw, ret)
|
2011-05-30 22:44:25 +00:00
|
|
|
}
|
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
func (sh *Handler) serveRecentPermanodes(rw http.ResponseWriter, req *http.Request) {
|
2011-06-12 07:20:57 +00:00
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
|
2011-03-14 00:14:48 +00:00
|
|
|
ch := make(chan *Result)
|
|
|
|
errch := make(chan os.Error)
|
|
|
|
go func() {
|
2011-05-30 05:52:31 +00:00
|
|
|
errch <- sh.index.GetRecentPermanodes(ch, []*blobref.BlobRef{sh.owner}, 50)
|
2011-03-14 00:14:48 +00:00
|
|
|
}()
|
2011-03-14 02:27:59 +00:00
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
dr := sh.NewDescribeRequest()
|
2011-06-12 07:20:57 +00:00
|
|
|
|
|
|
|
recent := jsonMapList()
|
2011-03-14 00:14:48 +00:00
|
|
|
for res := range ch {
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.Describe(res.BlobRef, 2)
|
2011-06-09 19:55:38 +00:00
|
|
|
jm := jsonMap()
|
2011-03-14 00:14:48 +00:00
|
|
|
jm["blobref"] = res.BlobRef.String()
|
2011-03-14 02:27:59 +00:00
|
|
|
jm["owner"] = res.Signer.String()
|
2011-03-14 00:14:48 +00:00
|
|
|
t := time.SecondsToUTC(res.LastModTime)
|
|
|
|
jm["modtime"] = t.Format(time.RFC3339)
|
2011-06-12 07:20:57 +00:00
|
|
|
recent = append(recent, jm)
|
2011-03-14 00:14:48 +00:00
|
|
|
}
|
2011-03-14 02:27:59 +00:00
|
|
|
|
2011-03-14 00:14:48 +00:00
|
|
|
err := <-errch
|
2011-03-14 02:27:59 +00:00
|
|
|
if err != nil {
|
|
|
|
// TODO: return error status code
|
2011-07-02 15:15:00 +00:00
|
|
|
ret["error"] = err.String()
|
2011-06-12 07:20:57 +00:00
|
|
|
return
|
2011-03-14 02:27:59 +00:00
|
|
|
}
|
2011-06-12 07:20:57 +00:00
|
|
|
|
|
|
|
ret["recent"] = recent
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.PopulateJSON(ret)
|
2011-05-30 22:44:25 +00:00
|
|
|
}
|
|
|
|
|
2011-07-02 15:15:00 +00:00
|
|
|
func (sh *Handler) serveTaggedPermanodes(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
|
|
|
|
signer := blobref.MustParse(mustGet(req, "signer"))
|
|
|
|
value := mustGet(req, "value")
|
|
|
|
ch := make(chan *blobref.BlobRef, buffered)
|
|
|
|
errch := make(chan os.Error)
|
|
|
|
go func() {
|
2011-07-05 17:30:22 +00:00
|
|
|
errch <- sh.index.GetTaggedPermanodes(ch, signer, value, maxPermanodes)
|
2011-07-02 15:15:00 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
dr := sh.NewDescribeRequest()
|
|
|
|
|
|
|
|
tagged := jsonMapList()
|
|
|
|
for res := range ch {
|
|
|
|
dr.Describe(res, 2)
|
|
|
|
jm := jsonMap()
|
|
|
|
jm["permanode"] = res.String()
|
|
|
|
tagged = append(tagged, jm)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := <-errch
|
|
|
|
if err != nil {
|
2011-08-25 13:15:38 +00:00
|
|
|
// TODO(mpl): return error status code, in addition to the english error code
|
2011-07-02 15:15:00 +00:00
|
|
|
ret["error"] = err.String()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ret["tagged"] = tagged
|
|
|
|
dr.PopulateJSON(ret)
|
|
|
|
}
|
|
|
|
|
2011-08-25 13:15:38 +00:00
|
|
|
// TODO(mpl): configure and/or document the name of the possible attributes in the http request
|
|
|
|
func (sh *Handler) serveRequestedPermanodes(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
|
|
|
|
signer := blobref.MustParse(mustGet(req, "signer"))
|
|
|
|
value := mustGet(req, "value")
|
|
|
|
fuzzy := req.FormValue("fuzzy") // exact match if empty
|
|
|
|
fuzzyMatch := false
|
|
|
|
if fuzzy != "" {
|
|
|
|
lowered := strings.ToLower(fuzzy)
|
|
|
|
if lowered == "true" || lowered == "t" {
|
|
|
|
fuzzyMatch = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
attr := req.FormValue("attr") // all attributes if empty
|
|
|
|
if attr == "" { // and force fuzzy in that case.
|
|
|
|
fuzzyMatch = true
|
|
|
|
}
|
|
|
|
maxResults := maxPermanodes
|
|
|
|
max := req.FormValue("max")
|
|
|
|
if max != "" {
|
|
|
|
maxR, err := strconv.Atoi(max)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Invalid specified max results 'max': " + err.String())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if maxR < maxResults {
|
|
|
|
maxResults = maxR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan *blobref.BlobRef, buffered)
|
|
|
|
errch := make(chan os.Error)
|
|
|
|
go func() {
|
|
|
|
errch <- sh.index.SearchPermanodes(ch,
|
|
|
|
&PermanodesRequest{Attribute: attr,
|
|
|
|
Query: value,
|
|
|
|
Signer: signer,
|
|
|
|
FuzzyMatch: fuzzyMatch,
|
|
|
|
MaxResults: maxResults})
|
|
|
|
}()
|
|
|
|
|
|
|
|
dr := sh.NewDescribeRequest()
|
|
|
|
|
|
|
|
requested := jsonMapList()
|
|
|
|
for res := range ch {
|
|
|
|
dr.Describe(res, 2)
|
|
|
|
jm := jsonMap()
|
|
|
|
jm["permanode"] = res.String()
|
|
|
|
requested = append(requested, jm)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := <-errch
|
|
|
|
if err != nil {
|
|
|
|
// TODO(mpl): return error status code, in addition to the english error code
|
|
|
|
ret["error"] = err.String()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ret["requested"] = requested
|
|
|
|
dr.PopulateJSON(ret)
|
|
|
|
}
|
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
func (sh *Handler) serveClaims(rw http.ResponseWriter, req *http.Request) {
|
2011-06-09 19:55:38 +00:00
|
|
|
ret := jsonMap()
|
2011-06-03 22:23:23 +00:00
|
|
|
|
|
|
|
pn := blobref.Parse(req.FormValue("permanode"))
|
|
|
|
if pn == nil {
|
|
|
|
http.Error(rw, "Missing or invalid 'permanode' param", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: rename GetOwnerClaims to GetClaims?
|
|
|
|
claims, err := sh.index.GetOwnerClaims(pn, sh.owner)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error getting claims of %s: %v", pn.String(), err)
|
|
|
|
} else {
|
|
|
|
sort.Sort(claims)
|
2011-06-09 19:55:38 +00:00
|
|
|
jclaims := jsonMapList()
|
2011-06-03 22:23:23 +00:00
|
|
|
|
|
|
|
for _, claim := range claims {
|
2011-06-09 19:55:38 +00:00
|
|
|
jclaim := jsonMap()
|
2011-06-03 22:23:23 +00:00
|
|
|
jclaim["blobref"] = claim.BlobRef.String()
|
|
|
|
jclaim["signer"] = claim.Signer.String()
|
|
|
|
jclaim["permanode"] = claim.Permanode.String()
|
|
|
|
jclaim["date"] = claim.Date.Format(time.RFC3339)
|
|
|
|
jclaim["type"] = claim.Type
|
|
|
|
if claim.Attr != "" {
|
|
|
|
jclaim["attr"] = claim.Attr
|
|
|
|
}
|
|
|
|
if claim.Value != "" {
|
|
|
|
jclaim["value"] = claim.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
jclaims = append(jclaims, jclaim)
|
|
|
|
}
|
|
|
|
ret["claims"] = jclaims
|
|
|
|
}
|
|
|
|
|
|
|
|
httputil.ReturnJson(rw, ret)
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
type DescribeRequest struct {
|
2011-06-17 03:45:47 +00:00
|
|
|
sh *Handler
|
2011-06-12 07:20:57 +00:00
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
lk sync.Mutex // protects following:
|
|
|
|
m map[string]*DescribedBlob
|
|
|
|
done map[string]bool // blobref -> described
|
|
|
|
errs map[string]os.Error // blobref -> error
|
2011-06-12 07:20:57 +00:00
|
|
|
|
|
|
|
wg *sync.WaitGroup // for load requests
|
|
|
|
}
|
|
|
|
|
2011-07-03 00:16:30 +00:00
|
|
|
// Given a blobref string returns a Description or nil.
|
|
|
|
// dr may be nil itself.
|
|
|
|
func (dr *DescribeRequest) DescribedBlobStr(blobstr string) *DescribedBlob {
|
|
|
|
if dr == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
dr.lk.Lock()
|
|
|
|
defer dr.lk.Unlock()
|
|
|
|
return dr.m[blobstr]
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:25:18 +00:00
|
|
|
type DescribedBlob struct {
|
2011-07-03 00:16:30 +00:00
|
|
|
Request *DescribeRequest
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
BlobRef *blobref.BlobRef
|
|
|
|
MimeType string
|
|
|
|
CamliType string
|
|
|
|
// TODO: just int is probably fine, if we're going to be capping blobs at 32MB?
|
|
|
|
Size int64
|
2011-06-30 23:25:18 +00:00
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
// if camliType "permanode"
|
|
|
|
Permanode *DescribedPermanode
|
|
|
|
|
|
|
|
// if camliType "file"
|
|
|
|
File *FileInfo
|
2011-07-03 00:16:30 +00:00
|
|
|
|
|
|
|
Stub bool // if not loaded, but referenced
|
|
|
|
}
|
|
|
|
|
2011-07-03 06:32:42 +00:00
|
|
|
// PermanodeFile returns the blobref path from this permanode to its
|
|
|
|
// File camliContent, else (nil, false)
|
|
|
|
func (b *DescribedBlob) PermanodeFile() (path []*blobref.BlobRef, fi *FileInfo, ok bool) {
|
|
|
|
if b == nil || b.Permanode == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if contentRef := b.Permanode.Attr.Get("camliContent"); contentRef != "" {
|
|
|
|
if cdes := b.Request.DescribedBlobStr(contentRef); cdes != nil && cdes.File != nil {
|
|
|
|
return []*blobref.BlobRef{b.BlobRef, cdes.BlobRef}, cdes.File, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
func (b *DescribedBlob) DomID() string {
|
|
|
|
if b == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return b.BlobRef.DomID()
|
|
|
|
}
|
|
|
|
|
2011-07-03 00:16:30 +00:00
|
|
|
func (b *DescribedBlob) Title() string {
|
|
|
|
if b == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if b.Permanode != nil {
|
|
|
|
if t := b.Permanode.Attr.Get("title"); t != "" {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
if contentRef := b.Permanode.Attr.Get("camliContent"); contentRef != "" {
|
|
|
|
return b.Request.DescribedBlobStr(contentRef).Title()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if b.File != nil {
|
|
|
|
return b.File.FileName
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *DescribedBlob) Description() string {
|
|
|
|
if b == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if b.Permanode != nil {
|
|
|
|
return b.Permanode.Attr.Get("description")
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *DescribedBlob) Members() []*DescribedBlob {
|
|
|
|
if b == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
m := make([]*DescribedBlob, 0)
|
|
|
|
if b.Permanode != nil {
|
|
|
|
for _, bstr := range b.Permanode.Attr["camliMember"] {
|
|
|
|
if br := blobref.Parse(bstr); br != nil {
|
2011-07-03 19:28:39 +00:00
|
|
|
m = append(m, b.PeerBlob(br))
|
2011-07-03 00:16:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
func (b *DescribedBlob) ContentRef() (br *blobref.BlobRef, ok bool) {
|
|
|
|
if b != nil && b.Permanode != nil {
|
|
|
|
if cref := b.Permanode.Attr.Get("camliContent"); cref != "" {
|
|
|
|
br = blobref.Parse(cref)
|
|
|
|
return br, br != nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-03 19:28:39 +00:00
|
|
|
func (b *DescribedBlob) PeerBlob(br *blobref.BlobRef) *DescribedBlob {
|
2011-07-03 00:16:30 +00:00
|
|
|
if b.Request == nil {
|
|
|
|
return &DescribedBlob{BlobRef: br, Stub: true}
|
|
|
|
}
|
|
|
|
b.Request.lk.Lock()
|
|
|
|
defer b.Request.lk.Unlock()
|
|
|
|
if peer, ok := b.Request.m[br.String()]; ok {
|
|
|
|
return peer
|
|
|
|
}
|
|
|
|
return &DescribedBlob{Request: b.Request, BlobRef: br, Stub: true}
|
2011-06-30 23:25:18 +00:00
|
|
|
}
|
|
|
|
|
2011-07-03 19:28:39 +00:00
|
|
|
// HasSecureLinkTo returns true if there's a valid link from this blob
|
|
|
|
// to the other blob. This is used in access control (hence the
|
|
|
|
// somewhat redundant "Secure" in the name) and should be paranoid
|
|
|
|
// against e.g. random user/attacker-control attributes making links
|
|
|
|
// to other blobs.
|
2011-07-07 01:38:27 +00:00
|
|
|
//
|
|
|
|
// TODO: don't linear scan here. rewrite this in terms of ResolvePrefixHop,
|
|
|
|
// passing down some policy perhaps? or maybe that's enough.
|
2011-07-03 19:28:39 +00:00
|
|
|
func (b *DescribedBlob) HasSecureLinkTo(other *blobref.BlobRef) bool {
|
|
|
|
if b == nil || other == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
ostr := other.String()
|
|
|
|
if b.Permanode != nil {
|
|
|
|
if b.Permanode.Attr.Get("camliContent") == ostr {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, mstr := range b.Permanode.Attr["camliMember"] {
|
|
|
|
if mstr == ostr {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (b *DescribedBlob) jsonMap() map[string]interface{} {
|
|
|
|
m := jsonMap()
|
|
|
|
m["blobRef"] = b.BlobRef.String()
|
|
|
|
if b.MimeType != "" {
|
|
|
|
m["mimeType"] = b.MimeType
|
|
|
|
}
|
|
|
|
if b.CamliType != "" {
|
|
|
|
m["camliType"] = b.CamliType
|
|
|
|
}
|
|
|
|
m["size"] = b.Size
|
|
|
|
if b.Permanode != nil {
|
|
|
|
m["permanode"] = b.Permanode.jsonMap()
|
|
|
|
}
|
|
|
|
if b.File != nil {
|
|
|
|
m["file"] = b.File
|
|
|
|
}
|
|
|
|
return m
|
2011-06-30 23:25:18 +00:00
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
type DescribedPermanode struct {
|
2011-08-25 15:14:47 +00:00
|
|
|
Attr url.Values // a map[string][]string
|
2011-06-30 23:25:18 +00:00
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (dp *DescribedPermanode) jsonMap() map[string]interface{} {
|
|
|
|
m := jsonMap()
|
|
|
|
|
|
|
|
am := jsonMap()
|
|
|
|
m["attr"] = am
|
|
|
|
for k, vv := range dp.Attr {
|
|
|
|
if len(vv) > 0 {
|
|
|
|
vl := make([]string, len(vv))
|
|
|
|
copy(vl[:], vv[:])
|
|
|
|
am[k] = vl
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
2011-07-02 02:36:22 +00:00
|
|
|
|
|
|
|
// NewDescribeRequest returns a new DescribeRequest holding the state
|
|
|
|
// of blobs and their summarized descriptions. Use DescribeBlob
|
|
|
|
// one or more times before calling PopulateJSON or Result.
|
2011-06-30 23:14:58 +00:00
|
|
|
func (sh *Handler) NewDescribeRequest() *DescribeRequest {
|
|
|
|
return &DescribeRequest{
|
2011-06-30 23:25:18 +00:00
|
|
|
sh: sh,
|
2011-07-01 21:33:15 +00:00
|
|
|
m: make(map[string]*DescribedBlob),
|
2011-06-30 23:25:18 +00:00
|
|
|
errs: make(map[string]os.Error),
|
|
|
|
wg: new(sync.WaitGroup),
|
2011-06-30 23:14:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-07 01:38:27 +00:00
|
|
|
// Given a blobref and a few hex characters of the digest of the next hop, return the complete
|
|
|
|
// blobref of the prefix, if that's a valid next hop.
|
|
|
|
func (sh *Handler) ResolvePrefixHop(parent *blobref.BlobRef, prefix string) (child *blobref.BlobRef, err os.Error) {
|
2011-07-06 00:27:10 +00:00
|
|
|
// TODO: this is a linear scan right now. this should be
|
|
|
|
// optimized to use a new database table of members so this is
|
|
|
|
// a quick lookup. in the meantime it should be in memcached
|
|
|
|
// at least.
|
|
|
|
if len(prefix) < 8 {
|
|
|
|
return nil, fmt.Errorf("Member prefix %q too small", prefix)
|
|
|
|
}
|
|
|
|
dr := sh.NewDescribeRequest()
|
|
|
|
dr.Describe(parent, 1)
|
|
|
|
res, err := dr.Result()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
des, ok := res[parent.String()]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Failed to describe member %q in parent %q", prefix, parent)
|
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
if des.Permanode != nil {
|
|
|
|
if cr, ok := des.ContentRef(); ok && strings.HasPrefix(cr.Digest(), prefix) {
|
|
|
|
return cr, nil
|
|
|
|
}
|
|
|
|
for _, member := range des.Members() {
|
|
|
|
if strings.HasPrefix(member.BlobRef.Digest(), prefix) {
|
|
|
|
return member.BlobRef, nil
|
|
|
|
}
|
2011-07-06 00:27:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("Member prefix %q not found in %q", prefix, parent)
|
|
|
|
}
|
|
|
|
|
2011-07-02 02:36:22 +00:00
|
|
|
type DescribeError map[string]os.Error
|
|
|
|
|
2011-07-03 00:16:30 +00:00
|
|
|
func (de DescribeError) String() string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
for b, err := range de {
|
|
|
|
fmt.Fprintf(&buf, "%s: %v; ", b, err)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("Errors (%d) describing blobs: %s", len(de), buf.String())
|
2011-07-02 02:36:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Result waits for all outstanding lookups to complete and
|
|
|
|
// returns the map of blobref (strings) to their described
|
|
|
|
// results. The returned error is non-nil if any errors
|
2011-07-03 00:16:30 +00:00
|
|
|
// occured, and will be of type DescribeError.
|
|
|
|
func (dr *DescribeRequest) Result() (desmap map[string]*DescribedBlob, err os.Error) {
|
2011-07-02 02:36:22 +00:00
|
|
|
dr.wg.Wait()
|
|
|
|
// TODO: set "done" / locked flag, so no more DescribeBlob can
|
|
|
|
// be called.
|
|
|
|
if len(dr.errs) > 0 {
|
2011-07-03 00:16:30 +00:00
|
|
|
return dr.m, DescribeError(dr.errs)
|
2011-07-02 02:36:22 +00:00
|
|
|
}
|
2011-07-03 00:16:30 +00:00
|
|
|
return dr.m, nil
|
2011-07-02 02:36:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PopulateJSON waits for all outstanding lookups to complete and populates
|
|
|
|
// the results into the provided dest map, suitable for marshalling
|
|
|
|
// as JSON with the json package.
|
2011-06-30 23:14:58 +00:00
|
|
|
func (dr *DescribeRequest) PopulateJSON(dest map[string]interface{}) {
|
|
|
|
dr.wg.Wait()
|
|
|
|
dr.lk.Lock()
|
|
|
|
defer dr.lk.Unlock()
|
|
|
|
for k, v := range dr.m {
|
2011-07-01 21:33:15 +00:00
|
|
|
dest[k] = v.jsonMap()
|
2011-06-30 23:14:58 +00:00
|
|
|
}
|
2011-06-30 23:25:18 +00:00
|
|
|
for k, err := range dr.errs {
|
|
|
|
dest["error"] = "error populating " + k + ": " + err.String()
|
|
|
|
break // TODO: include all?
|
|
|
|
}
|
2011-06-30 23:14:58 +00:00
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (dr *DescribeRequest) describedBlob(b *blobref.BlobRef) *DescribedBlob {
|
2011-06-12 07:20:57 +00:00
|
|
|
dr.lk.Lock()
|
|
|
|
defer dr.lk.Unlock()
|
|
|
|
bs := b.String()
|
2011-07-01 21:33:15 +00:00
|
|
|
if des, ok := dr.m[bs]; ok {
|
|
|
|
return des
|
2011-06-12 07:20:57 +00:00
|
|
|
}
|
2011-07-03 00:16:30 +00:00
|
|
|
des := &DescribedBlob{Request: dr, BlobRef: b}
|
2011-07-01 21:33:15 +00:00
|
|
|
dr.m[bs] = des
|
|
|
|
return des
|
2011-06-12 07:20:57 +00:00
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
func (dr *DescribeRequest) DescribeSync(br *blobref.BlobRef) (*DescribedBlob, os.Error) {
|
|
|
|
dr.Describe(br, 1)
|
|
|
|
res, err := dr.Result()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return res[br.String()], nil
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
func (dr *DescribeRequest) Describe(br *blobref.BlobRef, depth int) {
|
2011-06-12 07:20:57 +00:00
|
|
|
if depth <= 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dr.lk.Lock()
|
|
|
|
defer dr.lk.Unlock()
|
|
|
|
if dr.done == nil {
|
|
|
|
dr.done = make(map[string]bool)
|
|
|
|
}
|
|
|
|
if dr.done[br.String()] {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dr.done[br.String()] = true
|
|
|
|
dr.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer dr.wg.Done()
|
|
|
|
dr.describeReally(br, depth)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (dr *DescribeRequest) addError(br *blobref.BlobRef, err os.Error) {
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dr.lk.Lock()
|
|
|
|
defer dr.lk.Unlock()
|
|
|
|
// TODO: append? meh.
|
|
|
|
dr.errs[br.String()] = err
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
func (dr *DescribeRequest) describeReally(br *blobref.BlobRef, depth int) {
|
2011-06-12 07:20:57 +00:00
|
|
|
mime, size, err := dr.sh.index.GetBlobMimeType(br)
|
|
|
|
if err == os.ENOENT {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
2011-07-01 21:33:15 +00:00
|
|
|
dr.addError(br, err)
|
2011-06-12 07:20:57 +00:00
|
|
|
return
|
|
|
|
}
|
2011-06-30 23:25:18 +00:00
|
|
|
|
|
|
|
// TODO: convert all this in terms of
|
|
|
|
// DescribedBlob/DescribedPermanode/DescribedFile, not json
|
|
|
|
// maps. Then add JSON marhsallers to those types. Add tests.
|
2011-07-01 21:33:15 +00:00
|
|
|
des := dr.describedBlob(br)
|
|
|
|
des.setMimeType(mime)
|
|
|
|
des.Size = size
|
|
|
|
|
|
|
|
switch des.CamliType {
|
|
|
|
case "permanode":
|
|
|
|
des.Permanode = new(DescribedPermanode)
|
|
|
|
dr.populatePermanodeFields(des.Permanode, br, dr.sh.owner, depth)
|
|
|
|
case "file":
|
|
|
|
var err os.Error
|
|
|
|
des.File, err = dr.sh.index.GetFileInfo(br)
|
|
|
|
if err != nil {
|
|
|
|
dr.addError(br, err)
|
|
|
|
}
|
2011-06-12 07:20:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
func (sh *Handler) serveDescribe(rw http.ResponseWriter, req *http.Request) {
|
2011-06-09 19:55:38 +00:00
|
|
|
ret := jsonMap()
|
2011-06-11 16:29:41 +00:00
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
|
2011-05-30 22:44:25 +00:00
|
|
|
br := blobref.Parse(req.FormValue("blobref"))
|
|
|
|
if br == nil {
|
2011-06-11 16:29:41 +00:00
|
|
|
ret["error"] = "Missing or invalid 'blobref' param"
|
|
|
|
ret["errorType"] = "input"
|
2011-05-30 22:44:25 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
dr := sh.NewDescribeRequest()
|
|
|
|
dr.Describe(br, 4)
|
|
|
|
dr.PopulateJSON(ret)
|
2011-03-13 23:38:32 +00:00
|
|
|
}
|
2011-03-14 02:27:59 +00:00
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
func (sh *Handler) serveFiles(rw http.ResponseWriter, req *http.Request) {
|
2011-06-09 19:55:38 +00:00
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
|
|
|
|
br := blobref.Parse(req.FormValue("bytesref"))
|
|
|
|
if br == nil {
|
|
|
|
// TODO: formalize how errors are returned And make
|
|
|
|
// ReturnJson set the HTTP status to 400 automatically
|
|
|
|
// in some cases, if errorType is "input"? Document
|
|
|
|
// this somewhere. Are there existing JSON
|
|
|
|
// conventions to use?
|
|
|
|
ret["error"] = "Missing or invalid 'bytesref' param"
|
|
|
|
ret["errorType"] = "input"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
files, err := sh.index.ExistingFileSchemas(br)
|
|
|
|
if err != nil {
|
|
|
|
ret["error"] = err.String()
|
|
|
|
ret["errorType"] = "server"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
strList := []string{}
|
|
|
|
for _, br := range files {
|
|
|
|
strList = append(strList, br.String())
|
|
|
|
}
|
|
|
|
ret["files"] = strList
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (dr *DescribeRequest) populatePermanodeFields(pi *DescribedPermanode, pn, signer *blobref.BlobRef, depth int) {
|
2011-08-25 15:14:47 +00:00
|
|
|
pi.Attr = make(url.Values)
|
2011-07-01 21:33:15 +00:00
|
|
|
attr := pi.Attr
|
2011-03-14 03:51:58 +00:00
|
|
|
|
2011-06-12 07:20:57 +00:00
|
|
|
claims, err := dr.sh.index.GetOwnerClaims(pn, signer)
|
2011-03-14 03:51:58 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error getting claims of %s: %v", pn.String(), err)
|
2011-07-01 21:33:15 +00:00
|
|
|
dr.addError(pn, fmt.Errorf("Error getting claims of %s: %v", pn.String(), err))
|
2011-06-11 16:51:08 +00:00
|
|
|
return
|
|
|
|
}
|
2011-06-12 07:20:57 +00:00
|
|
|
|
2011-06-11 16:51:08 +00:00
|
|
|
sort.Sort(claims)
|
|
|
|
claimLoop:
|
|
|
|
for _, cl := range claims {
|
|
|
|
switch cl.Type {
|
|
|
|
case "del-attribute":
|
|
|
|
if cl.Value == "" {
|
2011-03-14 03:51:58 +00:00
|
|
|
attr[cl.Attr] = nil, false
|
2011-06-11 16:51:08 +00:00
|
|
|
} else {
|
2011-07-01 21:33:15 +00:00
|
|
|
sl := attr[cl.Attr]
|
|
|
|
filtered := make([]string, 0, len(sl))
|
|
|
|
for _, val := range sl {
|
|
|
|
if val != cl.Value {
|
|
|
|
filtered = append(filtered, val)
|
2011-06-04 17:18:38 +00:00
|
|
|
}
|
2011-06-11 16:51:08 +00:00
|
|
|
}
|
2011-07-01 21:33:15 +00:00
|
|
|
attr[cl.Attr] = filtered
|
2011-06-11 16:51:08 +00:00
|
|
|
}
|
|
|
|
case "set-attribute":
|
|
|
|
attr[cl.Attr] = nil, false
|
|
|
|
fallthrough
|
|
|
|
case "add-attribute":
|
|
|
|
if cl.Value == "" {
|
|
|
|
continue
|
|
|
|
}
|
2011-07-01 21:33:15 +00:00
|
|
|
sl, ok := attr[cl.Attr]
|
2011-06-11 16:51:08 +00:00
|
|
|
if ok {
|
|
|
|
for _, exist := range sl {
|
|
|
|
if exist == cl.Value {
|
|
|
|
continue claimLoop
|
|
|
|
}
|
2011-03-14 03:51:58 +00:00
|
|
|
}
|
2011-06-11 16:51:08 +00:00
|
|
|
} else {
|
|
|
|
sl = make([]string, 0, 1)
|
|
|
|
attr[cl.Attr] = sl
|
2011-03-14 03:51:58 +00:00
|
|
|
}
|
2011-06-11 16:51:08 +00:00
|
|
|
attr[cl.Attr] = append(sl, cl.Value)
|
2011-03-14 03:51:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the content permanode is now known, look up its type
|
2011-07-01 21:33:15 +00:00
|
|
|
if content, ok := attr["camliContent"]; ok && len(content) > 0 {
|
2011-06-11 16:51:08 +00:00
|
|
|
cbr := blobref.Parse(content[len(content)-1])
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.Describe(cbr, depth-1)
|
2011-05-30 22:44:25 +00:00
|
|
|
}
|
2011-06-11 17:12:39 +00:00
|
|
|
|
|
|
|
// Resolve children
|
2011-07-01 21:33:15 +00:00
|
|
|
if members, ok := attr["camliMember"]; ok {
|
|
|
|
for _, member := range members {
|
2011-06-12 07:20:57 +00:00
|
|
|
membr := blobref.Parse(member)
|
|
|
|
if membr != nil {
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.Describe(membr, depth-1)
|
2011-06-11 17:12:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-05-30 22:44:25 +00:00
|
|
|
}
|
|
|
|
|
2011-06-23 05:21:18 +00:00
|
|
|
func mustGet(req *http.Request, param string) string {
|
|
|
|
v := req.FormValue(param)
|
|
|
|
if v == "" {
|
|
|
|
panic(fmt.Sprintf("missing required parameter %q", param))
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func setPanicError(m map[string]interface{}) {
|
|
|
|
p := recover()
|
|
|
|
if p == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m["error"] = p.(string)
|
|
|
|
m["errorType"] = "input"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sh *Handler) serveSignerAttrValue(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
defer setPanicError(ret)
|
|
|
|
|
|
|
|
signer := blobref.MustParse(mustGet(req, "signer"))
|
|
|
|
attr := mustGet(req, "attr")
|
|
|
|
value := mustGet(req, "value")
|
|
|
|
pn, err := sh.index.PermanodeOfSignerAttrValue(signer, attr, value)
|
|
|
|
if err != nil {
|
|
|
|
ret["error"] = err.String()
|
|
|
|
} else {
|
|
|
|
ret["permanode"] = pn.String()
|
|
|
|
|
2011-06-30 23:14:58 +00:00
|
|
|
dr := sh.NewDescribeRequest()
|
|
|
|
dr.Describe(pn, 2)
|
|
|
|
dr.PopulateJSON(ret)
|
2011-06-23 05:21:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-26 00:50:38 +00:00
|
|
|
func (sh *Handler) serveSignerPaths(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
ret := jsonMap()
|
|
|
|
defer httputil.ReturnJson(rw, ret)
|
|
|
|
defer setPanicError(ret)
|
|
|
|
|
|
|
|
signer := blobref.MustParse(mustGet(req, "signer"))
|
|
|
|
target := blobref.MustParse(mustGet(req, "target"))
|
|
|
|
paths, err := sh.index.PathsOfSignerTarget(signer, target)
|
|
|
|
if err != nil {
|
|
|
|
ret["error"] = err.String()
|
|
|
|
} else {
|
|
|
|
jpaths := []map[string]interface{}{}
|
|
|
|
for _, path := range paths {
|
|
|
|
jpaths = append(jpaths, map[string]interface{}{
|
|
|
|
"claimRef": path.Claim.String(),
|
2011-06-30 23:14:58 +00:00
|
|
|
"baseRef": path.Base.String(),
|
|
|
|
"suffix": path.Suffix,
|
2011-06-26 00:50:38 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
ret["paths"] = jpaths
|
2011-06-30 23:14:58 +00:00
|
|
|
dr := sh.NewDescribeRequest()
|
2011-06-26 00:50:38 +00:00
|
|
|
for _, path := range paths {
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.Describe(path.Base, 2)
|
2011-06-26 00:50:38 +00:00
|
|
|
}
|
2011-06-30 23:14:58 +00:00
|
|
|
dr.PopulateJSON(ret)
|
2011-06-26 00:50:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-30 22:44:25 +00:00
|
|
|
const camliTypePrefix = "application/json; camliType="
|
|
|
|
|
2011-07-01 21:33:15 +00:00
|
|
|
func (d *DescribedBlob) setMimeType(mime string) {
|
|
|
|
d.MimeType = mime
|
2011-05-30 22:44:25 +00:00
|
|
|
if strings.HasPrefix(mime, camliTypePrefix) {
|
2011-07-01 21:33:15 +00:00
|
|
|
d.CamliType = mime[len(camliTypePrefix):]
|
2011-03-14 03:51:58 +00:00
|
|
|
}
|
2011-03-14 02:27:59 +00:00
|
|
|
}
|