package http import ( "bytes" "context" "fmt" "io" "net/http" "net/url" "golang.org/x/exp/slices" "source.toby3d.me/toby3d/auth/internal/client" "source.toby3d.me/toby3d/auth/internal/common" "source.toby3d.me/toby3d/auth/internal/domain" "source.toby3d.me/toby3d/auth/internal/httputil" ) type httpClientRepository struct { client *http.Client } const ( DefaultMaxRedirectsCount int = 10 hApp string = "h-app" hXApp string = "h-x-app" propertyLogo string = "logo" propertyName string = "name" propertyURL string = "url" relRedirectURI string = "redirect_uri" ) func NewHTTPClientRepository(c *http.Client) client.Repository { return &httpClientRepository{ client: c, } } // WARN(toby3d): not implemented. func (httpClientRepository) Create(_ context.Context, _ domain.Client) error { return nil } func (repo httpClientRepository) Get(ctx context.Context, cid domain.ClientID) (*domain.Client, error) { resp, err := repo.client.Get(cid.String()) if err != nil { return nil, fmt.Errorf("failed to make a request to the client: %w", err) } if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("%w: status on client page is not 200", client.ErrNotExist) } client := &domain.Client{ ID: cid, RedirectURI: make([]*url.URL, 0), Logo: make([]*url.URL, 0), URL: make([]*url.URL, 0), Name: make([]string, 0), } extract(resp.Body, resp.Request.URL, client, resp.Header.Get(common.HeaderLink)) return client, nil } //nolint:gocognit,cyclop func extract(r io.Reader, u *url.URL, dst *domain.Client, header string) { body, _ := io.ReadAll(r) for _, endpoint := range httputil.ExtractEndpoints(bytes.NewReader(body), u, header, relRedirectURI) { if !containsUrl(dst.RedirectURI, endpoint) { dst.RedirectURI = append(dst.RedirectURI, endpoint) } } for _, itemType := range []string{hApp, hXApp} { for _, name := range httputil.ExtractProperty(bytes.NewReader(body), u, itemType, propertyName) { if n, ok := name.(string); ok && !slices.Contains(dst.Name, n) { dst.Name = append(dst.Name, n) } } for _, logo := range httputil.ExtractProperty(bytes.NewReader(body), u, itemType, propertyLogo) { var logoURL *url.URL var err error switch l := logo.(type) { case string: logoURL, err = url.Parse(l) case map[string]string: if value, ok := l["value"]; ok { logoURL, err = url.Parse(value) } } if err != nil || containsUrl(dst.Logo, logoURL) { continue } dst.Logo = append(dst.Logo, logoURL) } for _, property := range httputil.ExtractProperty(bytes.NewReader(body), u, itemType, propertyURL) { prop, ok := property.(string) if !ok { continue } if u, err := url.Parse(prop); err == nil && !containsUrl(dst.URL, u) { dst.URL = append(dst.URL, u) } } } } func containsUrl(src []*url.URL, find *url.URL) bool { for i := range src { if src[i].String() != find.String() { continue } return true } return false }