1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package search
18
19 import (
20 "bytes"
21 "context"
22 "encoding/json"
23 "errors"
24 "fmt"
25 "io"
26 "log"
27 "net/http"
28 "net/url"
29 "regexp"
30 "sort"
31 "strconv"
32 "strings"
33 "time"
34
35 "go4.org/jsonconfig"
36 "go4.org/types"
37 "perkeep.org/internal/httputil"
38 "perkeep.org/pkg/blob"
39 "perkeep.org/pkg/blobserver"
40 "perkeep.org/pkg/index"
41 "perkeep.org/pkg/jsonsign"
42 "perkeep.org/pkg/schema"
43 "perkeep.org/pkg/types/camtypes"
44 "perkeep.org/pkg/types/serverconfig"
45 )
46
47 const buffered = 32
48 const maxResults = 1000
49 const defaultNumResults = 50
50
51
52
53 const MaxImageSize = 2000
54
55 var blobRefPattern = regexp.MustCompile(blob.Pattern)
56
57 func init() {
58 blobserver.RegisterHandlerConstructor("search", newHandlerFromConfig)
59 }
60
61 var (
62 _ QueryDescriber = (*Handler)(nil)
63 )
64
65
66 type Handler struct {
67 index index.Interface
68 owner *index.Owner
69
70 fetcher blob.Fetcher
71
72
73
74
75
76 corpus *index.Corpus
77
78 lh *index.LocationHelper
79
80
81 wsHub *wsHub
82 }
83
84
85 type GetRecentPermanoder interface {
86
87
88
89 GetRecentPermanodes(context.Context, *RecentRequest) (*RecentResponse, error)
90 }
91
92 var _ GetRecentPermanoder = (*Handler)(nil)
93
94 func NewHandler(ix index.Interface, owner *index.Owner) *Handler {
95 sh := &Handler{
96 index: ix,
97 owner: owner,
98 }
99 sh.lh = index.NewLocationHelper(sh.index.(*index.Index))
100 sh.wsHub = newWebsocketHub(sh)
101 go sh.wsHub.run()
102 sh.subscribeToNewBlobs()
103 return sh
104 }
105
106 func (h *Handler) InitHandler(lh blobserver.FindHandlerByTyper) error {
107 _, handler, err := lh.FindHandlerByType("storage-filesystem")
108 if err != nil || handler == nil {
109 return nil
110 }
111 h.fetcher = handler.(blob.Fetcher)
112 registerKeyword(newNamedSearch(h))
113 return nil
114 }
115
116 func (h *Handler) subscribeToNewBlobs() {
117 ch := make(chan blob.Ref, buffered)
118 blobserver.GetHub(h.index).RegisterListener(ch)
119 go func() {
120 ctx := context.Background()
121 for br := range ch {
122 h.index.RLock()
123 bm, err := h.index.GetBlobMeta(ctx, br)
124 if err == nil {
125 h.wsHub.newBlobRecv <- bm.CamliType
126 }
127 h.index.RUnlock()
128 }
129 }()
130 }
131
132 func (h *Handler) SetCorpus(c *index.Corpus) {
133 h.corpus = c
134 h.lh.SetCorpus(c)
135 }
136
137
138 func (h *Handler) SendStatusUpdate(status json.RawMessage) {
139 h.wsHub.statusUpdate <- status
140 }
141
142 func newHandlerFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) {
143 indexPrefix := conf.RequiredString("index")
144 ownerCfg := conf.RequiredObject("owner")
145 ownerId := ownerCfg.RequiredString("identity")
146 ownerSecring := ownerCfg.RequiredString("secringFile")
147
148 devBlockStartupPrefix := conf.OptionalString("devBlockStartupOn", "")
149 slurpToMemory := conf.OptionalBool("slurpToMemory", false)
150 if err := conf.Validate(); err != nil {
151 return nil, err
152 }
153
154 if devBlockStartupPrefix != "" {
155 _, err := ld.GetHandler(devBlockStartupPrefix)
156 if err != nil {
157 return nil, fmt.Errorf("search handler references bogus devBlockStartupOn handler %s: %v", devBlockStartupPrefix, err)
158 }
159 }
160
161 indexHandler, err := ld.GetHandler(indexPrefix)
162 if err != nil {
163 return nil, fmt.Errorf("search config references unknown handler %q", indexPrefix)
164 }
165 indexer, ok := indexHandler.(index.Interface)
166 if !ok {
167 return nil, fmt.Errorf("search config references invalid indexer %q (actually a %T)", indexPrefix, indexHandler)
168 }
169
170 owner, err := newOwner(serverconfig.Owner{
171 Identity: ownerId,
172 SecringFile: ownerSecring,
173 })
174 if err != nil {
175 return nil, fmt.Errorf("could not create Owner %v", err)
176 }
177 h := NewHandler(indexer, owner)
178
179 if slurpToMemory {
180 ii := indexer.(*index.Index)
181 ii.Lock()
182 corpus, err := ii.KeepInMemory()
183 if err != nil {
184 ii.Unlock()
185 return nil, fmt.Errorf("error slurping index to memory: %v", err)
186 }
187 h.SetCorpus(corpus)
188 ii.Unlock()
189 }
190
191 return h, nil
192 }
193
194 func newOwner(ownerCfg serverconfig.Owner) (*index.Owner, error) {
195 entity, err := jsonsign.EntityFromSecring(ownerCfg.Identity, ownerCfg.SecringFile)
196 if err != nil {
197 return nil, err
198 }
199 armoredPublicKey, err := jsonsign.ArmoredPublicKey(entity)
200 if err != nil {
201 return nil, err
202 }
203 return index.NewOwner(ownerCfg.Identity, blob.RefFromString(armoredPublicKey)), nil
204 }
205
206
207
208
209
210 func (h *Handler) Owner() blob.Ref {
211
212
213 return h.owner.BlobRef()
214 }
215
216 func (h *Handler) Index() index.Interface {
217 return h.index
218 }
219
220
221 func (h *Handler) HasLegacySHA1() bool {
222 idx, ok := h.index.(*index.Index)
223 if !ok {
224 log.Printf("Cannot guess for legacy SHA1 because we don't have an *index.Index")
225 return false
226 }
227 ok, err := idx.HasLegacySHA1()
228 if err != nil {
229 log.Printf("Cannot guess for legacy SHA1: %v", err)
230 return false
231 }
232 return ok
233 }
234
235 var getHandler = map[string]func(*Handler, http.ResponseWriter, *http.Request){
236 "ws": (*Handler).serveWebSocket,
237 "recent": (*Handler).serveRecentPermanodes,
238 "permanodeattr": (*Handler).servePermanodesWithAttr,
239 "describe": (*Handler).serveDescribe,
240 "claims": (*Handler).serveClaims,
241 "files": (*Handler).serveFiles,
242 "signerattrvalue": (*Handler).serveSignerAttrValue,
243 "signerpaths": (*Handler).serveSignerPaths,
244 "edgesto": (*Handler).serveEdgesTo,
245 }
246
247 var postHandler = map[string]func(*Handler, http.ResponseWriter, *http.Request){
248 "describe": (*Handler).serveDescribe,
249 "query": (*Handler).serveQuery,
250 }
251
252 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
253 suffix := httputil.PathSuffix(req)
254
255 handlers := getHandler
256 switch {
257 case httputil.IsGet(req):
258
259 case req.Method == "POST":
260 handlers = postHandler
261 default:
262 handlers = nil
263 }
264 fn := handlers[strings.TrimPrefix(suffix, "camli/search/")]
265 if fn != nil {
266 fn(h, rw, req)
267 return
268 }
269
270
271 ret := camtypes.SearchErrorResponse{
272 Error: "Unsupported search path or method",
273 ErrorType: "input",
274 }
275 httputil.ReturnJSON(rw, &ret)
276 }
277
278
279 func sanitizeNumResults(n int) int {
280 if n <= 0 || n > maxResults {
281 return defaultNumResults
282 }
283 return n
284 }
285
286
287 type RecentRequest struct {
288 N int
289 Before time.Time
290 }
291
292 func (r *RecentRequest) URLSuffix() string {
293 var buf bytes.Buffer
294 fmt.Fprintf(&buf, "camli/search/recent?n=%d", r.n())
295 if !r.Before.IsZero() {
296 fmt.Fprintf(&buf, "&before=%s", types.Time3339(r.Before))
297 }
298 return buf.String()
299 }
300
301
302 func (r *RecentRequest) fromHTTP(req *http.Request) {
303 r.N, _ = strconv.Atoi(req.FormValue("n"))
304 if before := req.FormValue("before"); before != "" {
305 r.Before = time.Time(types.ParseTime3339OrZero(before))
306 }
307 }
308
309
310 func (r *RecentRequest) n() int {
311 return sanitizeNumResults(r.N)
312 }
313
314
315 type WithAttrRequest struct {
316 N int
317 Signer blob.Ref
318
319
320 Attr string
321
322
323 Value string
324 Fuzzy bool
325
326
327 At time.Time
328 }
329
330 func (r *WithAttrRequest) URLSuffix() string {
331 s := fmt.Sprintf("camli/search/permanodeattr?signer=%v&value=%v&fuzzy=%v&attr=%v&max=%v",
332 r.Signer, url.QueryEscape(r.Value), r.Fuzzy, r.Attr, r.N)
333 if !r.At.IsZero() {
334 s += fmt.Sprintf("&at=%s", types.Time3339(r.At))
335 }
336 return s
337 }
338
339
340 func (r *WithAttrRequest) fromHTTP(req *http.Request) {
341 r.Signer = blob.ParseOrZero(req.FormValue("signer"))
342 r.Value = req.FormValue("value")
343 fuzzy := req.FormValue("fuzzy")
344 fuzzyMatch := false
345 if fuzzy != "" {
346 lowered := strings.ToLower(fuzzy)
347 if lowered == "true" || lowered == "t" {
348 fuzzyMatch = true
349 }
350 }
351 r.Attr = req.FormValue("attr")
352 if r.Attr == "" {
353 fuzzyMatch = true
354 }
355 r.Fuzzy = fuzzyMatch
356 max := req.FormValue("max")
357 if max != "" {
358 maxR, err := strconv.Atoi(max)
359 if err != nil {
360 panic(httputil.InvalidParameterError("max"))
361 }
362 r.N = maxR
363 }
364 r.N = r.n()
365 if at := req.FormValue("at"); at != "" {
366 r.At = time.Time(types.ParseTime3339OrZero(at))
367 }
368 }
369
370
371 func (r *WithAttrRequest) n() int {
372 return sanitizeNumResults(r.N)
373 }
374
375
376 type ClaimsRequest struct {
377 Permanode blob.Ref
378
379
380
381 AttrFilter string
382 }
383
384 func (r *ClaimsRequest) URLSuffix() string {
385 return fmt.Sprintf("camli/search/claims?permanode=%v&attrFilter=%s",
386 r.Permanode, url.QueryEscape(r.AttrFilter))
387 }
388
389
390 func (r *ClaimsRequest) fromHTTP(req *http.Request) {
391 r.Permanode = httputil.MustGetBlobRef(req, "permanode")
392 r.AttrFilter = req.FormValue("attrFilter")
393 }
394
395
396 type SignerPathsRequest struct {
397 Signer blob.Ref
398 Target blob.Ref
399 }
400
401
402 func (r *SignerPathsRequest) fromHTTP(req *http.Request) {
403 r.Signer = httputil.MustGetBlobRef(req, "signer")
404 r.Target = httputil.MustGetBlobRef(req, "target")
405 }
406
407
408 type EdgesRequest struct {
409
410 ToRef blob.Ref
411 }
412
413
414 func (r *EdgesRequest) fromHTTP(req *http.Request) {
415 r.ToRef = httputil.MustGetBlobRef(req, "blobref")
416 }
417
418
419
420
421
422 type RecentResponse struct {
423 Recent []*RecentItem `json:"recent"`
424 Meta MetaMap `json:"meta"`
425
426 Error string `json:"error,omitempty"`
427 ErrorType string `json:"errorType,omitempty"`
428 }
429
430 func (r *RecentResponse) Err() error {
431 if r.Error != "" || r.ErrorType != "" {
432 if r.ErrorType != "" {
433 return fmt.Errorf("%s: %s", r.ErrorType, r.Error)
434 }
435 return errors.New(r.Error)
436 }
437 return nil
438 }
439
440
441 type WithAttrResponse struct {
442 WithAttr []*WithAttrItem `json:"withAttr"`
443 Meta MetaMap `json:"meta"`
444
445 Error string `json:"error,omitempty"`
446 ErrorType string `json:"errorType,omitempty"`
447 }
448
449 func (r *WithAttrResponse) Err() error {
450 if r.Error != "" || r.ErrorType != "" {
451 if r.ErrorType != "" {
452 return fmt.Errorf("%s: %s", r.ErrorType, r.Error)
453 }
454 return errors.New(r.Error)
455 }
456 return nil
457 }
458
459
460 type ClaimsResponse struct {
461 Claims []*ClaimsItem `json:"claims"`
462 }
463
464
465 type SignerPathsResponse struct {
466 Paths []*SignerPathsItem `json:"paths"`
467 Meta MetaMap `json:"meta"`
468 }
469
470
471 type RecentItem struct {
472 BlobRef blob.Ref `json:"blobref"`
473 ModTime types.Time3339 `json:"modtime"`
474 Owner blob.Ref `json:"owner"`
475 }
476
477
478 type WithAttrItem struct {
479 Permanode blob.Ref `json:"permanode"`
480 }
481
482
483 type ClaimsItem struct {
484 BlobRef blob.Ref `json:"blobref"`
485 Signer blob.Ref `json:"signer"`
486 Permanode blob.Ref `json:"permanode"`
487 Date types.Time3339 `json:"date"`
488 Type string `json:"type"`
489 Attr string `json:"attr,omitempty"`
490 Value string `json:"value,omitempty"`
491 }
492
493
494 type SignerPathsItem struct {
495 ClaimRef blob.Ref `json:"claimRef"`
496 BaseRef blob.Ref `json:"baseRef"`
497 Suffix string `json:"suffix"`
498 }
499
500
501 type EdgesResponse struct {
502 ToRef blob.Ref `json:"toRef"`
503 EdgesTo []*EdgeItem `json:"edgesTo"`
504 }
505
506
507 type EdgeItem struct {
508 From blob.Ref `json:"from"`
509 FromType schema.CamliType `json:"fromType"`
510 }
511
512 var testHookBug121 = func() {}
513
514
515 func (h *Handler) GetRecentPermanodes(ctx context.Context, req *RecentRequest) (*RecentResponse, error) {
516 h.index.RLock()
517 defer h.index.RUnlock()
518
519 ch := make(chan camtypes.RecentPermanode)
520 errch := make(chan error, 1)
521 before := time.Now()
522 if !req.Before.IsZero() {
523 before = req.Before
524 }
525 go func() {
526
527
528 errch <- h.index.GetRecentPermanodes(ctx, ch, h.owner.BlobRef(), req.n(), before)
529 }()
530
531 dr := h.NewDescribeRequest()
532
533 var recent []*RecentItem
534 for res := range ch {
535 dr.StartDescribe(ctx, res.Permanode, 2)
536 recent = append(recent, &RecentItem{
537 BlobRef: res.Permanode,
538 Owner: res.Signer,
539 ModTime: types.Time3339(res.LastModTime),
540 })
541 testHookBug121()
542 }
543
544 if err := <-errch; err != nil {
545 return nil, err
546 }
547
548 metaMap, err := dr.metaMap()
549 if err != nil {
550 return nil, err
551 }
552
553 res := &RecentResponse{
554 Recent: recent,
555 Meta: metaMap,
556 }
557 return res, nil
558 }
559
560 func (h *Handler) serveRecentPermanodes(rw http.ResponseWriter, req *http.Request) {
561 defer httputil.RecoverJSON(rw, req)
562 var rr RecentRequest
563 rr.fromHTTP(req)
564 res, err := h.GetRecentPermanodes(req.Context(), &rr)
565 if err != nil {
566 httputil.ServeJSONError(rw, err)
567 return
568 }
569 httputil.ReturnJSON(rw, res)
570 }
571
572
573
574
575 func (h *Handler) GetPermanodesWithAttr(req *WithAttrRequest) (*WithAttrResponse, error) {
576 ctx := context.TODO()
577
578 h.index.RLock()
579 defer h.index.RUnlock()
580
581 ch := make(chan blob.Ref, buffered)
582 errch := make(chan error, 1)
583 go func() {
584 signer := req.Signer
585 if !signer.Valid() {
586 signer = h.owner.BlobRef()
587 }
588 errch <- h.index.SearchPermanodesWithAttr(ctx, ch,
589 &camtypes.PermanodeByAttrRequest{
590 Attribute: req.Attr,
591 Query: req.Value,
592 Signer: signer,
593 FuzzyMatch: req.Fuzzy,
594 MaxResults: req.N,
595 At: req.At,
596 })
597 }()
598
599 dr := h.NewDescribeRequest()
600
601 var withAttr []*WithAttrItem
602 for res := range ch {
603 dr.StartDescribe(ctx, res, 2)
604 withAttr = append(withAttr, &WithAttrItem{
605 Permanode: res,
606 })
607 }
608
609 metaMap, err := dr.metaMap()
610 if err != nil {
611 return nil, err
612 }
613
614 if err := <-errch; err != nil {
615 return nil, err
616 }
617
618 res := &WithAttrResponse{
619 WithAttr: withAttr,
620 Meta: metaMap,
621 }
622 return res, nil
623 }
624
625
626
627
628
629 func (h *Handler) servePermanodesWithAttr(rw http.ResponseWriter, req *http.Request) {
630 defer httputil.RecoverJSON(rw, req)
631 var wr WithAttrRequest
632 wr.fromHTTP(req)
633 res, err := h.GetPermanodesWithAttr(&wr)
634 if err != nil {
635 httputil.ServeJSONError(rw, err)
636 return
637 }
638 httputil.ReturnJSON(rw, res)
639 }
640
641
642 func (h *Handler) GetClaims(req *ClaimsRequest) (*ClaimsResponse, error) {
643 if !req.Permanode.Valid() {
644 return nil, errors.New("error getting claims: nil permanode")
645 }
646 h.index.RLock()
647 defer h.index.RUnlock()
648
649 ctx := context.TODO()
650 var claims []camtypes.Claim
651 claims, err := h.index.AppendClaims(ctx, claims, req.Permanode, h.owner.KeyID(), req.AttrFilter)
652 if err != nil {
653 return nil, fmt.Errorf("Error getting claims of %s: %v", req.Permanode.String(), err)
654 }
655 sort.Sort(camtypes.ClaimsByDate(claims))
656 var jclaims []*ClaimsItem
657 for _, claim := range claims {
658 jclaim := &ClaimsItem{
659 BlobRef: claim.BlobRef,
660 Signer: claim.Signer,
661 Permanode: claim.Permanode,
662 Date: types.Time3339(claim.Date),
663 Type: claim.Type,
664 Attr: claim.Attr,
665 Value: claim.Value,
666 }
667 jclaims = append(jclaims, jclaim)
668 }
669
670 res := &ClaimsResponse{
671 Claims: jclaims,
672 }
673 return res, nil
674 }
675
676 func (h *Handler) serveClaims(rw http.ResponseWriter, req *http.Request) {
677 defer httputil.RecoverJSON(rw, req)
678
679 h.index.RLock()
680 defer h.index.RUnlock()
681
682 var cr ClaimsRequest
683 cr.fromHTTP(req)
684 res, err := h.GetClaims(&cr)
685 if err != nil {
686 httputil.ServeJSONError(rw, err)
687 return
688 }
689 httputil.ReturnJSON(rw, res)
690 }
691
692 func (h *Handler) serveFiles(rw http.ResponseWriter, req *http.Request) {
693 var ret camtypes.FileSearchResponse
694 defer httputil.ReturnJSON(rw, &ret)
695
696 h.index.RLock()
697 defer h.index.RUnlock()
698
699 if err := req.ParseForm(); err != nil {
700 ret.Error = err.Error()
701 ret.ErrorType = "input"
702 return
703 }
704 values, ok := req.Form["wholedigest"]
705 if !ok {
706 ret.Error = "Missing 'wholedigest' param"
707 ret.ErrorType = "input"
708 return
709 }
710 var digests []blob.Ref
711 for _, v := range values {
712 br, ok := blob.Parse(v)
713 if !ok {
714 ret.Error = "Invalid 'wholedigest' param"
715 ret.ErrorType = "input"
716 return
717 }
718 digests = append(digests, br)
719 }
720
721 files, err := h.index.ExistingFileSchemas(digests...)
722 if err != nil {
723 ret.Error = err.Error()
724 ret.ErrorType = "server"
725 }
726
727 if files == nil {
728 files = make(index.WholeRefToFile)
729 }
730 ret.Files = files
731 }
732
733
734 type SignerAttrValueResponse struct {
735 Permanode blob.Ref `json:"permanode"`
736 Meta MetaMap `json:"meta"`
737 }
738
739 func (h *Handler) serveSignerAttrValue(rw http.ResponseWriter, req *http.Request) {
740 defer httputil.RecoverJSON(rw, req)
741 ctx := context.TODO()
742 signer := httputil.MustGetBlobRef(req, "signer")
743 attr := httputil.MustGet(req, "attr")
744 value := httputil.MustGet(req, "value")
745
746 h.index.RLock()
747 defer h.index.RUnlock()
748
749 pn, err := h.index.PermanodeOfSignerAttrValue(ctx, signer, attr, value)
750 if err != nil {
751 httputil.ServeJSONError(rw, err)
752 return
753 }
754
755 dr := h.NewDescribeRequest()
756 dr.StartDescribe(ctx, pn, 2)
757 metaMap, err := dr.metaMap()
758 if err != nil {
759 httputil.ServeJSONError(rw, err)
760 return
761 }
762
763 httputil.ReturnJSON(rw, &SignerAttrValueResponse{
764 Permanode: pn,
765 Meta: metaMap,
766 })
767 }
768
769
770
771 func (h *Handler) EdgesTo(req *EdgesRequest) (*EdgesResponse, error) {
772 ctx := context.TODO()
773 h.index.RLock()
774 defer h.index.RUnlock()
775
776 toRef := req.ToRef
777 toRefStr := toRef.String()
778 var edgeItems []*EdgeItem
779
780 edges, err := h.index.EdgesTo(toRef, nil)
781 if err != nil {
782 panic(err)
783 }
784
785 type edgeOrError struct {
786 edge *EdgeItem
787 err error
788 }
789 resc := make(chan edgeOrError)
790 verify := func(edge *camtypes.Edge) {
791 db, err := h.NewDescribeRequest().DescribeSync(ctx, edge.From)
792 if err != nil {
793 resc <- edgeOrError{err: err}
794 return
795 }
796 found := false
797 if db.Permanode != nil {
798 for attr, vv := range db.Permanode.Attr {
799 if index.IsBlobReferenceAttribute(attr) {
800 for _, v := range vv {
801 if v == toRefStr {
802 found = true
803 }
804 }
805 }
806 }
807 }
808 var ei *EdgeItem
809 if found {
810 ei = &EdgeItem{
811 From: edge.From,
812 FromType: schema.TypePermanode,
813 }
814 }
815 resc <- edgeOrError{edge: ei}
816 }
817 verifying := 0
818 for _, edge := range edges {
819 if edge.FromType == schema.TypePermanode {
820 verifying++
821 go verify(edge)
822 continue
823 }
824 ei := &EdgeItem{
825 From: edge.From,
826 FromType: edge.FromType,
827 }
828 edgeItems = append(edgeItems, ei)
829 }
830 for i := 0; i < verifying; i++ {
831 res := <-resc
832 if res.err != nil {
833 return nil, res.err
834 }
835 if res.edge != nil {
836 edgeItems = append(edgeItems, res.edge)
837 }
838 }
839
840 return &EdgesResponse{
841 ToRef: toRef,
842 EdgesTo: edgeItems,
843 }, nil
844 }
845
846
847
848 func (h *Handler) serveEdgesTo(rw http.ResponseWriter, req *http.Request) {
849 defer httputil.RecoverJSON(rw, req)
850 var er EdgesRequest
851 er.fromHTTP(req)
852 res, err := h.EdgesTo(&er)
853 if err != nil {
854 httputil.ServeJSONError(rw, err)
855 return
856 }
857 httputil.ReturnJSON(rw, res)
858 }
859
860 func (h *Handler) serveQuery(rw http.ResponseWriter, req *http.Request) {
861 defer httputil.RecoverJSON(rw, req)
862
863 var sq SearchQuery
864 if err := sq.FromHTTP(req); err != nil {
865 httputil.ServeJSONError(rw, err)
866 return
867 }
868
869 sr, err := h.Query(req.Context(), &sq)
870 if err != nil {
871 httputil.ServeJSONError(rw, err)
872 return
873 }
874
875 httputil.ReturnJSON(rw, sr)
876 }
877
878
879 func (h *Handler) GetSignerPaths(req *SignerPathsRequest) (*SignerPathsResponse, error) {
880 ctx := context.TODO()
881 if !req.Signer.Valid() {
882 return nil, errors.New("error getting signer paths: nil signer")
883 }
884 if !req.Target.Valid() {
885 return nil, errors.New("error getting signer paths: nil target")
886 }
887 h.index.RLock()
888 defer h.index.RUnlock()
889
890 paths, err := h.index.PathsOfSignerTarget(ctx, req.Signer, req.Target)
891 if err != nil {
892 return nil, fmt.Errorf("Error getting paths of %s: %v", req.Target.String(), err)
893 }
894 var jpaths []*SignerPathsItem
895 for _, path := range paths {
896 jpaths = append(jpaths, &SignerPathsItem{
897 ClaimRef: path.Claim,
898 BaseRef: path.Base,
899 Suffix: path.Suffix,
900 })
901 }
902
903 dr := h.NewDescribeRequest()
904 for _, path := range paths {
905 dr.StartDescribe(ctx, path.Base, 2)
906 }
907 metaMap, err := dr.metaMap()
908 if err != nil {
909 return nil, err
910 }
911
912 res := &SignerPathsResponse{
913 Paths: jpaths,
914 Meta: metaMap,
915 }
916 return res, nil
917 }
918
919 func (h *Handler) serveSignerPaths(rw http.ResponseWriter, req *http.Request) {
920 defer httputil.RecoverJSON(rw, req)
921 var sr SignerPathsRequest
922 sr.fromHTTP(req)
923
924 res, err := h.GetSignerPaths(&sr)
925 if err != nil {
926 httputil.ServeJSONError(rw, err)
927 return
928 }
929 httputil.ReturnJSON(rw, res)
930 }
931
932
933
934 func evalSearchInput(in string) (*Constraint, error) {
935 if len(in) == 0 {
936 return nil, fmt.Errorf("empty expression")
937 }
938 if strings.HasPrefix(in, "{") && strings.HasSuffix(in, "}") {
939 cs := new(Constraint)
940 if err := json.NewDecoder(strings.NewReader(in)).Decode(&cs); err != nil {
941 return nil, err
942 }
943 return cs, nil
944 } else {
945 sq, err := parseExpression(context.TODO(), in)
946 if err != nil {
947 return nil, err
948 }
949 return sq.Constraint.Logical.B, nil
950 }
951 }
952
953
954 func (sh *Handler) getNamed(ctx context.Context, name string) (string, error) {
955 if sh.fetcher == nil {
956 return "", fmt.Errorf("GetNamed functionality not available")
957 }
958 sr, err := sh.Query(ctx, NamedSearch(name))
959 if err != nil {
960 return "", err
961 }
962
963 if len(sr.Blobs) < 1 {
964 return "", fmt.Errorf("No named search found for: %s", name)
965 }
966 permaRef := sr.Blobs[0].Blob
967 substRefS := sr.Describe.Meta.Get(permaRef).Permanode.Attr.Get("camliContent")
968 br, ok := blob.Parse(substRefS)
969 if !ok {
970 return "", fmt.Errorf("Invalid blob ref: %s", substRefS)
971 }
972
973 reader, _, err := sh.fetcher.Fetch(ctx, br)
974 if err != nil {
975 return "", err
976 }
977 result, err := io.ReadAll(reader)
978 if err != nil {
979 return "", err
980 }
981 return string(result), nil
982 }
983
984
985 func NamedSearch(name string) *SearchQuery {
986 return &SearchQuery{
987 Constraint: &Constraint{
988 Permanode: &PermanodeConstraint{
989 Attr: "camliNamedSearch",
990 Value: name,
991 },
992 },
993 Describe: &DescribeRequest{},
994 }
995 }
996
997 const camliTypePrefix = "application/json; camliType="