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