1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17
18 package client
19
20 import (
21 "bytes"
22 "context"
23 "crypto/tls"
24 "encoding/json"
25 "errors"
26 "fmt"
27 "io"
28 "log"
29 "net"
30 "net/http"
31 "net/url"
32 "os"
33 "regexp"
34 "slices"
35 "strings"
36 "sync"
37 "time"
38
39 "perkeep.org/internal/hashutil"
40 "perkeep.org/internal/httputil"
41 "perkeep.org/internal/osutil"
42 "perkeep.org/pkg/auth"
43 "perkeep.org/pkg/blob"
44 "perkeep.org/pkg/blobserver"
45 "perkeep.org/pkg/buildinfo"
46 "perkeep.org/pkg/client/android"
47 "perkeep.org/pkg/schema"
48 "perkeep.org/pkg/search"
49 "perkeep.org/pkg/types/camtypes"
50
51 "go4.org/syncutil"
52 "golang.org/x/net/http2"
53 )
54
55
56
57
58
59 type Client struct {
60
61
62
63
64
65
66 server string
67
68 prefixOnce syncutil.Once
69 prefixv string
70 isSharePrefix bool
71
72 discoOnce syncutil.Once
73 searchRoot string
74 downloadHelper string
75 storageGen string
76 hasLegacySHA1 bool
77 syncHandlers []*SyncInfo
78 serverKeyID string
79 helpRoot string
80 shareRoot string
81 serverPublicKeyBlobRef blob.Ref
82
83 signerOnce sync.Once
84 signer *schema.Signer
85 signerErr error
86 signHandler string
87
88 authMode auth.AuthMode
89
90
91 authErr error
92
93 httpClient *http.Client
94 haveCache HaveCache
95
96
97 sto blobserver.Storage
98
99 initTrustedCertsOnce sync.Once
100
101
102
103
104
105
106
107
108
109
110 trustedCerts []string
111
112
113
114 insecureAnyTLSCert bool
115
116 initIgnoredFilesOnce sync.Once
117
118
119
120
121 ignoredFiles []string
122 ignoreChecker func(path string) bool
123
124 initSignerPublicKeyBlobrefOnce sync.Once
125 signerPublicKeyRef blob.Ref
126 publicKeyArmored string
127
128 statsMutex sync.Mutex
129 stats Stats
130
131
132
133
134 viaMu sync.RWMutex
135 via map[blob.Ref]blob.Ref
136
137
138
139
140 Verbose bool
141
142
143
144
145 Logger *log.Logger
146
147 httpGate *syncutil.Gate
148 transportConfig *TransportConfig
149
150 noExtConfig bool
151
152
153
154
155
156 sameOrigin bool
157 }
158
159 const maxParallelHTTP_h1 = 5
160 const maxParallelHTTP_h2 = 50
161
162
163
164
165
166 func New(opts ...ClientOption) (*Client, error) {
167 c := &Client{
168 haveCache: noHaveCache{},
169 Logger: log.New(os.Stderr, "", log.Ldate|log.Ltime),
170 authMode: auth.None{},
171 }
172 for _, v := range opts {
173 v.modifyClient(c)
174 }
175 if c.sto != nil && len(opts) > 1 {
176 return nil, errors.New("use of OptionUseStorageClient precludes use of any other options")
177 }
178
179 if c.noExtConfig {
180 c.setDefaultHTTPClient()
181 return c, nil
182 }
183
184 if c.server != "" {
185 if !isURLOrHostPort(c.server) {
186 configOnce.Do(parseConfig)
187 serverConf, ok := config.Servers[c.server]
188 if !ok {
189 log.Fatalf("%q looks like a server alias, but no such alias found in config at %v", c.server, osutil.UserClientConfigPath())
190 }
191 c.server = serverConf.Server
192 }
193 c.setDefaultHTTPClient()
194 return c, nil
195 }
196
197 var err error
198 c.server, err = getServer()
199 if err != nil {
200 return nil, err
201 }
202 err = c.SetupAuth()
203 if err != nil {
204 return nil, err
205 }
206 c.setDefaultHTTPClient()
207 return c, nil
208 }
209
210 func (c *Client) setDefaultHTTPClient() {
211 if c.httpClient == nil {
212 c.httpClient = &http.Client{
213 Transport: c.transportForConfig(c.transportConfig),
214 }
215 }
216 c.httpGate = syncutil.NewGate(httpGateSize(c.httpClient.Transport))
217 }
218
219
220 func NewOrFail(opts ...ClientOption) *Client {
221 c, err := New(opts...)
222 if err != nil {
223 log.Fatalf("error creating client: %v", err)
224 }
225 return c
226 }
227
228
229 func (c *Client) NewPathClient(path string) (*Client, error) {
230 u, err := url.Parse(c.server)
231 if err != nil {
232 return nil, fmt.Errorf("bogus server %q for NewPathClient receiver: %w", c.server, err)
233 }
234 u.Path = path
235 pc, err := New(OptionServer(u.String()))
236 if err != nil {
237 return nil, err
238 }
239 pc.authMode = c.authMode
240 pc.discoOnce.Do(noop)
241 return pc, nil
242 }
243
244
245 type TransportConfig struct {
246
247
248 Proxy func(*http.Request) (*url.URL, error)
249 Verbose bool
250 }
251
252 func (c *Client) useHTTP2(tc *TransportConfig) bool {
253 if !c.useTLS() {
254 return false
255 }
256 if android.IsChild() {
257
258 return false
259 }
260 if os.Getenv("HTTPS_PROXY") != "" || os.Getenv("https_proxy") != "" ||
261 (tc != nil && tc.Proxy != nil) {
262
263 return false
264 }
265 return true
266 }
267
268
269
270
271
272 func (c *Client) transportForConfig(tc *TransportConfig) http.RoundTripper {
273 if c == nil {
274 return nil
275 }
276 var transport http.RoundTripper
277 proxy := http.ProxyFromEnvironment
278 if tc != nil && tc.Proxy != nil {
279 proxy = tc.Proxy
280 }
281
282 if c.useHTTP2(tc) {
283 transport = &http2.Transport{
284 DialTLS: c.http2DialTLSFunc(),
285 }
286 } else {
287 transport = &http.Transport{
288 DialTLS: c.DialTLSFunc(),
289 Dial: c.DialFunc(),
290 Proxy: proxy,
291 MaxIdleConnsPerHost: maxParallelHTTP_h1,
292 }
293 }
294 httpStats := &httputil.StatsTransport{
295 Transport: transport,
296 }
297 if tc != nil {
298 httpStats.VerboseLog = tc.Verbose
299 }
300 transport = httpStats
301 if android.IsChild() {
302 transport = &android.StatsTransport{Rt: transport}
303 }
304 return transport
305 }
306
307
308
309 func (c *Client) HTTPStats() *httputil.StatsTransport {
310 st, _ := c.httpClient.Transport.(*httputil.StatsTransport)
311 return st
312 }
313
314 type ClientOption interface {
315 modifyClient(*Client)
316 }
317
318
319
320
321
322
323
324
325 func OptionServer(server string) ClientOption {
326 return optionServer(server)
327 }
328
329 type optionServer string
330
331 func (s optionServer) modifyClient(c *Client) { c.server = string(s) }
332
333
334
335
336
337
338
339
340
341
342
343 func OptionUseStorageClient(s blobserver.Storage) ClientOption {
344 return optionStorage{s}
345 }
346
347 type optionStorage struct {
348 sto blobserver.Storage
349 }
350
351 func (o optionStorage) modifyClient(c *Client) {
352 c.sto = o.sto
353 c.noExtConfig = true
354 }
355
356
357
358 func OptionTransportConfig(tc *TransportConfig) ClientOption {
359 return optionTransportConfig{tc}
360 }
361
362 type optionTransportConfig struct {
363 tc *TransportConfig
364 }
365
366 func (o optionTransportConfig) modifyClient(c *Client) { c.transportConfig = o.tc }
367
368
369
370
371
372 func OptionInsecure(v bool) ClientOption {
373 return optionInsecure(v)
374 }
375
376 type optionInsecure bool
377
378 func (o optionInsecure) modifyClient(c *Client) { c.insecureAnyTLSCert = bool(o) }
379
380
381
382
383
384
385
386 func OptionTrustedCert(cert string) ClientOption {
387
388 return optionTrustedCert(cert)
389 }
390
391 type optionTrustedCert string
392
393 func (o optionTrustedCert) modifyClient(c *Client) {
394 cert := string(o)
395 if cert != "" {
396 c.initTrustedCertsOnce.Do(func() {})
397 c.trustedCerts = []string{string(o)}
398 }
399 }
400
401 type optionNoExtConfig bool
402
403 func (o optionNoExtConfig) modifyClient(c *Client) { c.noExtConfig = bool(o) }
404
405
406
407
408 func OptionNoExternalConfig() ClientOption {
409 return optionNoExtConfig(true)
410 }
411
412 type optionAuthMode struct {
413 m auth.AuthMode
414 }
415
416 func (o optionAuthMode) modifyClient(c *Client) { c.authMode = o.m }
417
418
419
420 func OptionAuthMode(m auth.AuthMode) ClientOption {
421 return optionAuthMode{m}
422 }
423
424
425 func noop() error { return nil }
426
427 var shareURLRx = regexp.MustCompile(`^(.+)/(` + blob.Pattern + ")$")
428
429
430
431 func NewFromShareRoot(ctx context.Context, shareBlobURL string, opts ...ClientOption) (c *Client, target blob.Ref, err error) {
432 var root string
433 m := shareURLRx.FindStringSubmatch(shareBlobURL)
434 if m == nil {
435 return nil, blob.Ref{}, fmt.Errorf("Unknown share URL base")
436 }
437 c, err = New(append(opts[:len(opts):cap(opts)], OptionServer(m[1]))...)
438 if err != nil {
439 return nil, blob.Ref{}, err
440 }
441 c.discoOnce.Do(noop)
442 c.prefixOnce.Do(noop)
443 c.prefixv = m[1]
444 c.isSharePrefix = true
445 c.authMode = auth.None{}
446 c.via = make(map[blob.Ref]blob.Ref)
447 root = m[2]
448
449 req := c.newRequest(ctx, "GET", shareBlobURL, nil)
450 res, err := c.expect2XX(req)
451 if err != nil {
452 return nil, blob.Ref{}, fmt.Errorf("error fetching %s: %w", shareBlobURL, err)
453 }
454 defer res.Body.Close()
455 var buf bytes.Buffer
456 rootbr, ok := blob.Parse(root)
457 if !ok {
458 return nil, blob.Ref{}, fmt.Errorf("invalid root blob ref for sharing: %q", root)
459 }
460 b, err := schema.BlobFromReader(rootbr, io.TeeReader(res.Body, &buf))
461 if err != nil {
462 return nil, blob.Ref{}, fmt.Errorf("error parsing JSON from %s: %w, with response: %q", shareBlobURL, err, buf.Bytes())
463 }
464 if b.ShareAuthType() != schema.ShareHaveRef {
465 return nil, blob.Ref{}, fmt.Errorf("unknown share authType of %q", b.ShareAuthType())
466 }
467 target = b.ShareTarget()
468 if !target.Valid() {
469 return nil, blob.Ref{}, fmt.Errorf("no target")
470 }
471 c.via[target] = rootbr
472 return c, target, nil
473 }
474
475
476
477 func (c *Client) SetHTTPClient(client *http.Client) {
478 if client == nil {
479 client = http.DefaultClient
480 }
481 c.httpClient = client
482 }
483
484
485 func (c *Client) HTTPClient() *http.Client {
486 return c.httpClient
487 }
488
489
490 type HaveCache interface {
491 StatBlobCache(br blob.Ref) (size uint32, ok bool)
492 NoteBlobExists(br blob.Ref, size uint32)
493 }
494
495 type noHaveCache struct{}
496
497 func (noHaveCache) StatBlobCache(blob.Ref) (uint32, bool) { return 0, false }
498 func (noHaveCache) NoteBlobExists(blob.Ref, uint32) {}
499
500 func (c *Client) SetHaveCache(cache HaveCache) {
501 if cache == nil {
502 cache = noHaveCache{}
503 }
504 c.haveCache = cache
505 }
506
507 func (c *Client) printf(format string, v ...interface{}) {
508 if c.Verbose && c.Logger != nil {
509 c.Logger.Printf(format, v...)
510 }
511 }
512
513 func (c *Client) Stats() Stats {
514 c.statsMutex.Lock()
515 defer c.statsMutex.Unlock()
516 return c.stats
517 }
518
519
520 var ErrNoSearchRoot = errors.New("client: server doesn't support search")
521
522
523 var ErrNoHelpRoot = errors.New("client: server does not have a help handler")
524
525
526 var ErrNoShareRoot = errors.New("client: server does not have a share handler")
527
528
529 var ErrNoSigning = fmt.Errorf("client: server doesn't support signing")
530
531
532
533 var ErrNoStorageGeneration = errors.New("client: server doesn't report a storage generation")
534
535
536 var ErrNoSync = errors.New("client: server has no sync handlers")
537
538
539
540
541
542 func (c *Client) BlobRoot() (string, error) {
543 prefix, err := c.prefix()
544 if err != nil {
545 return "", err
546 }
547 return prefix + "/", nil
548 }
549
550
551
552
553 func (c *Client) ServerKeyID() (string, error) {
554 if err := c.condDiscovery(); err != nil {
555 return "", err
556 }
557 if c.serverKeyID == "" {
558 return "", ErrNoSigning
559 }
560 return c.serverKeyID, nil
561 }
562
563
564
565 func (c *Client) ServerPublicKeyBlobRef() (blob.Ref, error) {
566 if err := c.condDiscovery(); err != nil {
567 return blob.Ref{}, err
568 }
569
570 if !c.serverPublicKeyBlobRef.Valid() {
571 return blob.Ref{}, ErrNoSigning
572 }
573 return c.serverPublicKeyBlobRef, nil
574 }
575
576
577
578
579 func (c *Client) SearchRoot() (string, error) {
580 if err := c.condDiscovery(); err != nil {
581 return "", err
582 }
583 if c.searchRoot == "" {
584 return "", ErrNoSearchRoot
585 }
586 return c.searchRoot, nil
587 }
588
589
590
591
592 func (c *Client) HelpRoot() (string, error) {
593 if err := c.condDiscovery(); err != nil {
594 return "", err
595 }
596 if c.helpRoot == "" {
597 return "", ErrNoHelpRoot
598 }
599 return c.helpRoot, nil
600 }
601
602
603
604
605 func (c *Client) ShareRoot() (string, error) {
606 if err := c.condDiscovery(); err != nil {
607 return "", err
608 }
609 if c.shareRoot == "" {
610 return "", ErrNoShareRoot
611 }
612 return c.shareRoot, nil
613 }
614
615
616
617
618 func (c *Client) SignHandler() (string, error) {
619 if err := c.condDiscovery(); err != nil {
620 return "", err
621 }
622 if c.signHandler == "" {
623 return "", ErrNoSigning
624 }
625 return c.signHandler, nil
626 }
627
628
629
630
631
632
633
634
635
636
637 func (c *Client) StorageGeneration() (string, error) {
638 if err := c.condDiscovery(); err != nil {
639 return "", err
640 }
641 if c.storageGen == "" {
642 return "", ErrNoStorageGeneration
643 }
644 return c.storageGen, nil
645 }
646
647
648 func (c *Client) HasLegacySHA1() (bool, error) {
649 if err := c.condDiscovery(); err != nil {
650 return false, err
651 }
652 return c.hasLegacySHA1, nil
653 }
654
655
656
657 type SyncInfo struct {
658 From string
659 To string
660 ToIndex bool
661 }
662
663
664
665
666
667 func (c *Client) SyncHandlers() ([]*SyncInfo, error) {
668 if err := c.condDiscovery(); err != nil {
669 return nil, err
670 }
671 if c.syncHandlers == nil {
672 return nil, ErrNoSync
673 }
674 return c.syncHandlers, nil
675 }
676
677 var _ search.GetRecentPermanoder = (*Client)(nil)
678
679
680 func (c *Client) GetRecentPermanodes(ctx context.Context, req *search.RecentRequest) (*search.RecentResponse, error) {
681 sr, err := c.SearchRoot()
682 if err != nil {
683 return nil, err
684 }
685 url := sr + req.URLSuffix()
686 hreq := c.newRequest(ctx, "GET", url)
687 hres, err := c.expect2XX(hreq)
688 if err != nil {
689 return nil, err
690 }
691 res := new(search.RecentResponse)
692 if err := httputil.DecodeJSON(hres, res); err != nil {
693 return nil, err
694 }
695 if err := res.Err(); err != nil {
696 return nil, err
697 }
698 return res, nil
699 }
700
701
702
703
704
705 func (c *Client) GetPermanodesWithAttr(ctx context.Context, req *search.WithAttrRequest) (*search.WithAttrResponse, error) {
706 sr, err := c.SearchRoot()
707 if err != nil {
708 return nil, err
709 }
710 url := sr + req.URLSuffix()
711 hreq := c.newRequest(ctx, "GET", url)
712 hres, err := c.expect2XX(hreq)
713 if err != nil {
714 return nil, err
715 }
716 res := new(search.WithAttrResponse)
717 if err := httputil.DecodeJSON(hres, res); err != nil {
718 return nil, err
719 }
720 if err := res.Err(); err != nil {
721 return nil, err
722 }
723 return res, nil
724 }
725
726 func (c *Client) Describe(ctx context.Context, req *search.DescribeRequest) (*search.DescribeResponse, error) {
727 sr, err := c.SearchRoot()
728 if err != nil {
729 return nil, err
730 }
731 url := sr + req.URLSuffixPost()
732 body, err := json.MarshalIndent(req, "", "\t")
733 if err != nil {
734 return nil, err
735 }
736 hreq := c.newRequest(ctx, "POST", url, bytes.NewReader(body))
737 hres, err := c.expect2XX(hreq)
738 if err != nil {
739 return nil, err
740 }
741 res := new(search.DescribeResponse)
742 if err := httputil.DecodeJSON(hres, res); err != nil {
743 return nil, err
744 }
745 return res, nil
746 }
747
748 func (c *Client) GetClaims(ctx context.Context, req *search.ClaimsRequest) (*search.ClaimsResponse, error) {
749 sr, err := c.SearchRoot()
750 if err != nil {
751 return nil, err
752 }
753 url := sr + req.URLSuffix()
754 hreq := c.newRequest(ctx, "GET", url)
755 hres, err := c.expect2XX(hreq)
756 if err != nil {
757 return nil, err
758 }
759 res := new(search.ClaimsResponse)
760 if err := httputil.DecodeJSON(hres, res); err != nil {
761 return nil, err
762 }
763 return res, nil
764 }
765
766 func (c *Client) query(ctx context.Context, req *search.SearchQuery) (*http.Response, error) {
767 sr, err := c.SearchRoot()
768 if err != nil {
769 return nil, err
770 }
771 url := sr + req.URLSuffix()
772 body, err := json.Marshal(req)
773 if err != nil {
774 return nil, err
775 }
776 hreq := c.newRequest(ctx, "POST", url, bytes.NewReader(body))
777 return c.expect2XX(hreq)
778 }
779
780 func (c *Client) Query(ctx context.Context, req *search.SearchQuery) (*search.SearchResult, error) {
781 hres, err := c.query(ctx, req)
782 if err != nil {
783 return nil, err
784 }
785 res := new(search.SearchResult)
786 if err := httputil.DecodeJSON(hres, res); err != nil {
787 return nil, err
788 }
789 return res, nil
790 }
791
792
793
794 func (c *Client) QueryRaw(ctx context.Context, req *search.SearchQuery) ([]byte, error) {
795 hres, err := c.query(ctx, req)
796 if err != nil {
797 return nil, err
798 }
799 defer hres.Body.Close()
800 return io.ReadAll(hres.Body)
801 }
802
803
804
805
806
807
808
809
810
811
812
813
814 func (c *Client) SearchExistingFileSchema(ctx context.Context, wholeRef ...blob.Ref) (blob.Ref, error) {
815 sr, err := c.SearchRoot()
816 if err != nil {
817 return blob.Ref{}, err
818 }
819 if len(wholeRef) == 0 {
820 return blob.Ref{}, nil
821 }
822 url := sr + "camli/search/files"
823 for i, ref := range wholeRef {
824 if i == 0 {
825 url += "?wholedigest=" + ref.String()
826 } else {
827 url += "&wholedigest=" + ref.String()
828 }
829 }
830 req := c.newRequest(ctx, "GET", url)
831 res, err := c.doReqGated(req)
832 if err != nil {
833 return blob.Ref{}, err
834 }
835 if res.StatusCode != 200 {
836 body, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
837 res.Body.Close()
838 return blob.Ref{}, fmt.Errorf("client: got status code %d from URL %s; body %s", res.StatusCode, url, body)
839 }
840 var ress camtypes.FileSearchResponse
841 if err := httputil.DecodeJSON(res, &ress); err != nil {
842
843 mismatch, err := c.versionMismatch(ctx)
844 if err != nil {
845 log.Printf("Could not verify whether client is too recent or server is too old: %v", err)
846 } else if mismatch {
847 return blob.Ref{}, fmt.Errorf("Client is too recent for this server. Use a client built before 2018-01-13-6e8a5930c9, or upgrade the server to after that revision.")
848 }
849 return blob.Ref{}, fmt.Errorf("client: error parsing JSON from URL %s: %w", url, err)
850 }
851 if len(ress.Files) == 0 {
852 return blob.Ref{}, nil
853 }
854 for wholeRef, files := range ress.Files {
855 for _, f := range files {
856 if c.FileHasContents(ctx, f, blob.MustParse(wholeRef)) {
857 return f, nil
858 }
859 }
860 }
861 return blob.Ref{}, nil
862 }
863
864
865
866 func (c *Client) versionMismatch(ctx context.Context) (bool, error) {
867 const shortRFC3339 = "2006-01-02"
868 version := buildinfo.GitInfo
869 if version == "" {
870 return false, errors.New("unknown client version")
871 }
872 version = version[:10]
873 clientDate, err := time.Parse(shortRFC3339, version)
874 if err != nil {
875 return false, fmt.Errorf("could not parse date from version %q: %w", version, err)
876 }
877 apiChangeDate, _ := time.Parse(shortRFC3339, "2018-01-13")
878 if !clientDate.After(apiChangeDate) {
879
880 return false, nil
881 }
882 url := c.discoRoot() + "/status/status.json"
883 req := c.newRequest(ctx, "GET", url)
884 res, err := c.doReqGated(req)
885 if err != nil {
886 return false, err
887 }
888 if res.StatusCode != 200 {
889 body, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
890 res.Body.Close()
891 return false, fmt.Errorf("got status code %d from URL %s; body %s", res.StatusCode, url, body)
892 }
893 var status struct {
894 Version string `json:"version"`
895 }
896 if err := httputil.DecodeJSON(res, &status); err != nil {
897 return false, fmt.Errorf("error parsing JSON from URL %s: %w", url, err)
898 }
899 serverVersion := status.Version[:10]
900 serverDate, err := time.Parse(shortRFC3339, serverVersion)
901 if err != nil {
902 return false, fmt.Errorf("could not parse date from server version %q: %w", status.Version, err)
903 }
904 if serverDate.After(apiChangeDate) {
905
906 return false, nil
907 }
908 return true, nil
909 }
910
911
912
913
914 func (c *Client) FileHasContents(ctx context.Context, f, wholeRef blob.Ref) bool {
915 if err := c.condDiscovery(); err != nil {
916 return false
917 }
918 if c.downloadHelper == "" {
919 return false
920 }
921 req := c.newRequest(ctx, "HEAD", c.downloadHelper+f.String()+"/?verifycontents="+wholeRef.String())
922 res, err := c.expect2XX(req)
923 if err != nil {
924 log.Printf("download helper HEAD error: %v", err)
925 return false
926 }
927 defer res.Body.Close()
928 return res.Header.Get("X-Camli-Contents") == wholeRef.String()
929 }
930
931
932
933
934 func (c *Client) prefix() (string, error) {
935 if err := c.prefixOnce.Do(c.initPrefix); err != nil {
936 return "", err
937 }
938 return c.prefixv, nil
939 }
940
941
942
943 func (c *Client) blobPrefix() (string, error) {
944 pfx, err := c.prefix()
945 if err != nil {
946 return "", err
947 }
948 if !c.isSharePrefix {
949 pfx += "/camli"
950 }
951 return pfx, nil
952 }
953
954
955 func (c *Client) discoRoot() string {
956 s := c.server
957 if c.sameOrigin {
958 s = strings.TrimPrefix(s, "http://")
959 s = strings.TrimPrefix(s, "https://")
960 parts := strings.SplitN(s, "/", 1)
961 if len(parts) < 2 {
962 return "/"
963 }
964 return "/" + parts[1]
965 }
966 if !strings.HasPrefix(s, "http") {
967 s = "https://" + s
968 }
969 return s
970 }
971
972
973
974
975
976 func (c *Client) initPrefix() error {
977 c.isSharePrefix = false
978 root := c.discoRoot()
979 u, err := url.Parse(root)
980 if err != nil {
981 return err
982 }
983 if len(u.Path) > 1 {
984 c.prefixv = strings.TrimRight(root, "/")
985 return nil
986 }
987 return c.condDiscovery()
988 }
989
990 func (c *Client) condDiscovery() error {
991 if c.sto != nil {
992 return errors.New("client not using HTTP")
993 }
994 return c.discoOnce.Do(c.doDiscovery)
995 }
996
997
998
999
1000 func (c *Client) DiscoveryDoc(ctx context.Context) (io.Reader, error) {
1001 res, err := c.discoveryResp(ctx)
1002 if err != nil {
1003 return nil, err
1004 }
1005 defer res.Body.Close()
1006 const maxSize = 1 << 20
1007 all, err := io.ReadAll(io.LimitReader(res.Body, maxSize+1))
1008 if err != nil {
1009 return nil, err
1010 }
1011 if len(all) > maxSize {
1012 return nil, errors.New("discovery document oddly large")
1013 }
1014 if len(all) > 0 && all[len(all)-1] != '\n' {
1015 all = append(all, '\n')
1016 }
1017 return bytes.NewReader(all), err
1018 }
1019
1020
1021 func (c *Client) HTTPVersion(ctx context.Context) (string, error) {
1022 req := c.newRequest(ctx, "HEAD", c.discoRoot(), nil)
1023 res, err := c.doReqGated(req)
1024 if err != nil {
1025 return "", err
1026 }
1027 return res.Proto, err
1028 }
1029
1030 func (c *Client) discoveryResp(ctx context.Context) (*http.Response, error) {
1031
1032
1033 req := c.newRequest(ctx, "GET", c.discoRoot(), nil)
1034 req.Header.Set("Accept", "text/x-camli-configuration")
1035 res, err := c.doReqGated(req)
1036 if err != nil {
1037 return nil, err
1038 }
1039 if res.StatusCode != 200 {
1040 res.Body.Close()
1041 errMsg := fmt.Sprintf("got status %q from blobserver URL %q during configuration discovery", res.Status, c.discoRoot())
1042 if res.StatusCode == http.StatusUnauthorized && c.authErr != nil {
1043 errMsg = fmt.Sprintf("%v. %v", c.authErr, errMsg)
1044 }
1045 return nil, errors.New(errMsg)
1046 }
1047
1048
1049
1050 if ct := res.Header.Get("Content-Type"); ct != "text/javascript" {
1051 res.Body.Close()
1052 return nil, fmt.Errorf("Blobserver returned unexpected type %q from discovery", ct)
1053 }
1054 return res, nil
1055 }
1056
1057 func (c *Client) doDiscovery() error {
1058 ctx := context.TODO()
1059 root, err := url.Parse(c.discoRoot())
1060 if err != nil {
1061 return err
1062 }
1063
1064 res, err := c.discoveryResp(ctx)
1065 if err != nil {
1066 return err
1067 }
1068
1069 var disco camtypes.Discovery
1070 if err := httputil.DecodeJSON(res, &disco); err != nil {
1071 return err
1072 }
1073
1074 if disco.SearchRoot == "" {
1075 c.searchRoot = ""
1076 } else {
1077 u, err := root.Parse(disco.SearchRoot)
1078 if err != nil {
1079 return fmt.Errorf("client: invalid searchRoot %q; failed to resolve", disco.SearchRoot)
1080 }
1081 c.searchRoot = u.String()
1082 }
1083
1084 u, err := root.Parse(disco.HelpRoot)
1085 if err != nil {
1086 return fmt.Errorf("client: invalid helpRoot %q; failed to resolve", disco.HelpRoot)
1087 }
1088 c.helpRoot = u.String()
1089
1090 u, err = root.Parse(disco.ShareRoot)
1091 if err != nil {
1092 return fmt.Errorf("client: invalid shareRoot %q; failed to resolve", disco.ShareRoot)
1093 }
1094 c.shareRoot = u.String()
1095
1096 c.storageGen = disco.StorageGeneration
1097 c.hasLegacySHA1 = disco.HasLegacySHA1Index
1098
1099 u, err = root.Parse(disco.BlobRoot)
1100 if err != nil {
1101 return fmt.Errorf("client: error resolving blobRoot: %w", err)
1102 }
1103 c.prefixv = strings.TrimRight(u.String(), "/")
1104
1105 if disco.UIDiscovery != nil {
1106 u, err = root.Parse(disco.DownloadHelper)
1107 if err != nil {
1108 return fmt.Errorf("client: invalid downloadHelper %q; failed to resolve", disco.DownloadHelper)
1109 }
1110 c.downloadHelper = u.String()
1111 }
1112
1113 if disco.SyncHandlers != nil {
1114 for _, v := range disco.SyncHandlers {
1115 ufrom, err := root.Parse(v.From)
1116 if err != nil {
1117 return fmt.Errorf("client: invalid %q \"from\" sync; failed to resolve", v.From)
1118 }
1119 uto, err := root.Parse(v.To)
1120 if err != nil {
1121 return fmt.Errorf("client: invalid %q \"to\" sync; failed to resolve", v.To)
1122 }
1123 c.syncHandlers = append(c.syncHandlers, &SyncInfo{
1124 From: ufrom.String(),
1125 To: uto.String(),
1126 ToIndex: v.ToIndex,
1127 })
1128 }
1129 }
1130
1131 if disco.Signing != nil {
1132 c.serverKeyID = disco.Signing.PublicKeyID
1133 c.serverPublicKeyBlobRef = disco.Signing.PublicKeyBlobRef
1134 c.signHandler = disco.Signing.SignHandler
1135 }
1136 return nil
1137 }
1138
1139
1140
1141
1142 func (c *Client) GetJSON(ctx context.Context, url string, data interface{}) error {
1143 if !strings.HasPrefix(url, c.discoRoot()) {
1144 return fmt.Errorf("wrong URL (%q) for this server", url)
1145 }
1146 hreq := c.newRequest(ctx, "GET", url)
1147 resp, err := c.expect2XX(hreq)
1148 if err != nil {
1149 return err
1150 }
1151 return httputil.DecodeJSON(resp, data)
1152 }
1153
1154
1155
1156
1157 func (c *Client) Post(ctx context.Context, url string, bodyType string, body io.Reader) error {
1158 resp, err := c.post(ctx, url, bodyType, body)
1159 if err != nil {
1160 return err
1161 }
1162 return resp.Body.Close()
1163 }
1164
1165
1166
1167
1168 func (c *Client) Sign(ctx context.Context, server string, r io.Reader) (signed []byte, err error) {
1169 signHandler, err := c.SignHandler()
1170 if err != nil {
1171 return nil, err
1172 }
1173 signServer := strings.TrimSuffix(server, "/") + signHandler
1174 resp, err := c.post(ctx, signServer, "application/x-www-form-urlencoded", r)
1175 if err != nil {
1176 return nil, err
1177 }
1178 defer resp.Body.Close()
1179 return io.ReadAll(resp.Body)
1180 }
1181
1182 func (c *Client) post(ctx context.Context, url string, bodyType string, body io.Reader) (*http.Response, error) {
1183 if !c.sameOrigin && !strings.HasPrefix(url, c.discoRoot()) {
1184 return nil, fmt.Errorf("wrong URL (%q) for this server", url)
1185 }
1186 req := c.newRequest(ctx, "POST", url, body)
1187 req.Header.Set("Content-Type", bodyType)
1188 res, err := c.expect2XX(req)
1189 if err != nil {
1190 return nil, err
1191 }
1192 return res, nil
1193 }
1194
1195
1196
1197 func (c *Client) newRequest(ctx context.Context, method, url string, body ...io.Reader) *http.Request {
1198 var bodyR io.Reader
1199 if len(body) > 0 {
1200 bodyR = body[0]
1201 }
1202 if len(body) > 1 {
1203 panic("too many body arguments")
1204 }
1205 req, err := http.NewRequest(method, url, bodyR)
1206 if err != nil {
1207 panic(err.Error())
1208 }
1209
1210 if br, ok := bodyR.(*bytes.Reader); ok {
1211 req.ContentLength = int64(br.Len())
1212 }
1213 c.authMode.AddAuthHeader(req)
1214 return req.WithContext(ctx)
1215 }
1216
1217
1218
1219 func (c *Client) expect2XX(req *http.Request) (*http.Response, error) {
1220 res, err := c.doReqGated(req)
1221 if err == nil && (res.StatusCode < 200 || res.StatusCode > 299) {
1222 buf := new(bytes.Buffer)
1223 io.CopyN(buf, res.Body, 1<<20)
1224 res.Body.Close()
1225 return res, fmt.Errorf("client: got status code %d from URL %s; body %s", res.StatusCode, req.URL.String(), buf.String())
1226 }
1227 return res, err
1228 }
1229
1230 func (c *Client) doReqGated(req *http.Request) (*http.Response, error) {
1231 c.httpGate.Start()
1232 defer c.httpGate.Done()
1233 return c.httpClient.Do(req)
1234 }
1235
1236
1237 func (c *Client) DialFunc() func(network, addr string) (net.Conn, error) {
1238 if c.useTLS() {
1239 return nil
1240 }
1241 if android.IsChild() {
1242 return func(network, addr string) (net.Conn, error) {
1243 return android.Dial(network, addr)
1244 }
1245 }
1246 return nil
1247 }
1248
1249 func (c *Client) http2DialTLSFunc() func(network, addr string, cfg *tls.Config) (net.Conn, error) {
1250 trustedCerts := c.getTrustedCerts()
1251 if !c.insecureAnyTLSCert && len(trustedCerts) == 0 {
1252
1253
1254 return nil
1255 }
1256 return func(network, addr string, cfg *tls.Config) (net.Conn, error) {
1257
1258 cfg.InsecureSkipVerify = true
1259 conn, err := tls.Dial(network, addr, cfg)
1260 if err != nil {
1261 return nil, err
1262 }
1263 if c.insecureAnyTLSCert {
1264 return conn, err
1265 }
1266 state := conn.ConnectionState()
1267 if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
1268 return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, http2.NextProtoTLS)
1269 }
1270 certs := state.PeerCertificates
1271 if len(certs) < 1 {
1272 return nil, fmt.Errorf("no TLS peer certificates from %s", addr)
1273 }
1274 sig := hashutil.SHA256Prefix(certs[0].Raw)
1275 if slices.Contains(trustedCerts, sig) {
1276 return conn, nil
1277 }
1278 return nil, fmt.Errorf("TLS server at %v presented untrusted certificate (signature %q)", addr, sig)
1279 }
1280 }
1281
1282
1283
1284
1285
1286
1287 func (c *Client) DialTLSFunc() func(network, addr string) (net.Conn, error) {
1288 if !c.useTLS() {
1289 return nil
1290 }
1291 trustedCerts := c.getTrustedCerts()
1292 var stdTLS bool
1293 if !c.insecureAnyTLSCert && len(trustedCerts) == 0 {
1294
1295 stdTLS = true
1296 if !android.IsChild() {
1297
1298 return nil
1299 }
1300 }
1301
1302 return func(network, addr string) (net.Conn, error) {
1303 var conn *tls.Conn
1304 var err error
1305 if android.IsChild() {
1306 ac, err := android.Dial(network, addr)
1307 if err != nil {
1308 return nil, err
1309 }
1310 var tlsConfig *tls.Config
1311 if stdTLS {
1312 tlsConfig, err = android.TLSConfig()
1313 if err != nil {
1314 return nil, err
1315 }
1316 } else {
1317 tlsConfig = &tls.Config{InsecureSkipVerify: true}
1318 }
1319
1320
1321
1322 tlsConfig.ServerName = c.serverNameOfAddr(addr)
1323 conn = tls.Client(ac, tlsConfig)
1324 if err := conn.Handshake(); err != nil {
1325 return nil, err
1326 }
1327 if stdTLS {
1328
1329
1330 return conn, nil
1331 }
1332 } else {
1333 conn, err = tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: true})
1334 if err != nil {
1335 return nil, err
1336 }
1337 }
1338 if c.insecureAnyTLSCert {
1339 return conn, nil
1340 }
1341 certs := conn.ConnectionState().PeerCertificates
1342 if len(certs) < 1 {
1343 return nil, fmt.Errorf("no TLS peer certificates from %s", addr)
1344 }
1345 sig := hashutil.SHA256Prefix(certs[0].Raw)
1346 if slices.Contains(trustedCerts, sig) {
1347 return conn, nil
1348 }
1349 return nil, fmt.Errorf("TLS server at %v presented untrusted certificate (signature %q)", addr, sig)
1350 }
1351 }
1352
1353
1354
1355
1356 func (c *Client) serverNameOfAddr(addr string) string {
1357 serverName, _, err := net.SplitHostPort(addr)
1358 if err != nil {
1359 c.printf("could not get server name from address %q: %v", addr, err)
1360 return ""
1361 }
1362 if ip := net.ParseIP(serverName); ip != nil {
1363 return ""
1364 }
1365 return serverName
1366 }
1367
1368
1369
1370 func (c *Client) Signer() (*schema.Signer, error) {
1371 c.signerOnce.Do(c.signerInit)
1372 return c.signer, c.signerErr
1373 }
1374
1375 func (c *Client) signerInit() {
1376 c.signer, c.signerErr = c.buildSigner()
1377 }
1378
1379 func (c *Client) buildSigner() (*schema.Signer, error) {
1380 c.initSignerPublicKeyBlobrefOnce.Do(c.initSignerPublicKeyBlobref)
1381 if !c.signerPublicKeyRef.Valid() {
1382 return nil, camtypes.ErrClientNoPublicKey
1383 }
1384 return schema.NewSigner(c.signerPublicKeyRef, strings.NewReader(c.publicKeyArmored), c.SecretRingFile())
1385 }
1386
1387
1388
1389 func (c *Client) signBlob(ctx context.Context, bb schema.Buildable, sigTime time.Time) (string, error) {
1390 signer, err := c.Signer()
1391 if err != nil {
1392 return "", err
1393 }
1394 return bb.Builder().SignAt(ctx, signer, sigTime)
1395 }
1396
1397
1398
1399
1400
1401 func (c *Client) UploadPublicKey(ctx context.Context) error {
1402 sigRef := c.SignerPublicKeyBlobref()
1403 if !sigRef.Valid() {
1404 return nil
1405 }
1406 var err error
1407 if _, keyUploaded := c.haveCache.StatBlobCache(sigRef); !keyUploaded {
1408 _, err = c.uploadString(ctx, c.publicKeyArmored, false)
1409 }
1410 return err
1411 }
1412
1413
1414 func (c *Client) checkMatchingKeys() {
1415 serverKey, err := c.ServerKeyID()
1416 if err != nil {
1417 log.Printf("Warning: Could not obtain ther server's key id: %v", err)
1418 return
1419 }
1420 if serverKey != c.signer.KeyIDLong() {
1421 log.Printf("Warning: client (%s) and server (%s) keys differ.", c.signer.KeyIDLong(), serverKey)
1422 }
1423 }
1424
1425 func (c *Client) UploadAndSignBlob(ctx context.Context, b schema.AnyBlob) (*PutResult, error) {
1426 signed, err := c.signBlob(ctx, b.Blob(), time.Time{})
1427 if err != nil {
1428 return nil, err
1429 }
1430 c.checkMatchingKeys()
1431 if err := c.UploadPublicKey(ctx); err != nil {
1432 return nil, err
1433 }
1434 return c.uploadString(ctx, signed, false)
1435 }
1436
1437 func (c *Client) UploadBlob(ctx context.Context, b schema.AnyBlob) (*PutResult, error) {
1438
1439
1440 return c.uploadString(ctx, b.Blob().JSON(), true)
1441 }
1442
1443 func (c *Client) uploadString(ctx context.Context, s string, stat bool) (*PutResult, error) {
1444 uh := NewUploadHandleFromString(s)
1445 uh.SkipStat = !stat
1446 return c.Upload(ctx, uh)
1447 }
1448
1449 func (c *Client) UploadNewPermanode(ctx context.Context) (*PutResult, error) {
1450 unsigned := schema.NewUnsignedPermanode()
1451 return c.UploadAndSignBlob(ctx, unsigned)
1452 }
1453
1454 func (c *Client) UploadPlannedPermanode(ctx context.Context, key string, sigTime time.Time) (*PutResult, error) {
1455 unsigned := schema.NewPlannedPermanode(key)
1456 signed, err := c.signBlob(ctx, unsigned, sigTime)
1457 if err != nil {
1458 return nil, err
1459 }
1460 c.checkMatchingKeys()
1461 if err := c.UploadPublicKey(ctx); err != nil {
1462 return nil, err
1463 }
1464 return c.uploadString(ctx, signed, true)
1465 }
1466
1467
1468
1469
1470
1471
1472
1473 func (c *Client) IsIgnoredFile(fullpath string) bool {
1474 c.initIgnoredFilesOnce.Do(c.initIgnoredFiles)
1475 return c.ignoreChecker(fullpath)
1476 }
1477
1478
1479 func (c *Client) Close() error {
1480 if cl, ok := c.sto.(io.Closer); ok {
1481 return cl.Close()
1482 }
1483 if c := c.HTTPClient(); c != nil {
1484 switch t := c.Transport.(type) {
1485 case *http.Transport:
1486 t.CloseIdleConnections()
1487 case *http2.Transport:
1488 t.CloseIdleConnections()
1489 }
1490 }
1491 return nil
1492 }
1493
1494 func httpGateSize(rt http.RoundTripper) int {
1495 switch v := rt.(type) {
1496 case *httputil.StatsTransport:
1497 return httpGateSize(v.Transport)
1498 case *http.Transport:
1499 return maxParallelHTTP_h1
1500 case *http2.Transport:
1501 return maxParallelHTTP_h2
1502 default:
1503 return maxParallelHTTP_h1
1504 }
1505 }