github.com/network-quality/goresponsiveness@v0.0.0-20240129151524-343954285090/config/config.go (about)

     1  /*
     2   * This file is part of Go Responsiveness.
     3   *
     4   * Go Responsiveness is free software: you can redistribute it and/or modify it under
     5   * the terms of the GNU General Public License as published by the Free Software Foundation,
     6   * either version 2 of the License, or (at your option) any later version.
     7   * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY
     8   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     9   * PARTICULAR PURPOSE. See the GNU General Public License for more details.
    10   *
    11   * You should have received a copy of the GNU General Public License along
    12   * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>.
    13   */
    14  
    15  package config
    16  
    17  import (
    18  	"crypto/tls"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  
    26  	"github.com/network-quality/goresponsiveness/utilities"
    27  )
    28  
    29  type ConfigUrls struct {
    30  	SmallUrl  string `json:"small_https_download_url"`
    31  	LargeUrl  string `json:"large_https_download_url"`
    32  	UploadUrl string `json:"https_upload_url"`
    33  }
    34  
    35  type Config struct {
    36  	Version       int
    37  	Urls          ConfigUrls `json:"urls"`
    38  	Source        string
    39  	ConnectToAddr string `json:"test_endpoint"`
    40  }
    41  
    42  func (c *Config) Get(configHost string, configPath string, insecureSkipVerify bool, keyLogger io.Writer) error {
    43  	configTransport := &http.Transport{
    44  		TLSClientConfig: &tls.Config{
    45  			InsecureSkipVerify: insecureSkipVerify,
    46  		},
    47  		Proxy: http.ProxyFromEnvironment,
    48  	}
    49  	if !utilities.IsInterfaceNil(keyLogger) {
    50  		configTransport.TLSClientConfig.KeyLogWriter = keyLogger
    51  	}
    52  
    53  	utilities.OverrideHostTransport(configTransport, c.ConnectToAddr)
    54  
    55  	configClient := &http.Client{Transport: configTransport}
    56  
    57  	// Extraneous /s in URLs is normally okay, but the Apple CDN does not
    58  	// like them. Make sure that we put exactly one (1) / between the host
    59  	// and the path.
    60  	if !strings.HasPrefix(configPath, "/") {
    61  		configPath = "/" + configPath
    62  	}
    63  
    64  	c.Source = fmt.Sprintf("https://%s%s", configHost, configPath)
    65  	req, err := http.NewRequest("GET", c.Source, nil)
    66  	if err != nil {
    67  		return fmt.Errorf(
    68  			"Error: Could not create request for configuration host %s: %v",
    69  			configHost,
    70  			err,
    71  		)
    72  	}
    73  
    74  	req.Header.Set("User-Agent", utilities.UserAgent())
    75  
    76  	resp, err := configClient.Do(req)
    77  	if err != nil {
    78  		return fmt.Errorf(
    79  			"Error: could not connect to configuration host %s: %v",
    80  			configHost,
    81  			err,
    82  		)
    83  	}
    84  	defer resp.Body.Close()
    85  
    86  	if resp.StatusCode != 200 {
    87  		return fmt.Errorf(
    88  			"Error: Configuration host %s returned %d for config request",
    89  			configHost,
    90  			resp.StatusCode,
    91  		)
    92  	}
    93  
    94  	jsonConfig, err := io.ReadAll(resp.Body)
    95  	if err != nil {
    96  		return fmt.Errorf(
    97  			"Error: Could not read configuration content downloaded from %s: %v",
    98  			c.Source,
    99  			err,
   100  		)
   101  	}
   102  
   103  	err = json.Unmarshal(jsonConfig, c)
   104  	if err != nil {
   105  		return fmt.Errorf(
   106  			"could not parse configuration returned from %s: %v",
   107  			c.Source,
   108  			err,
   109  		)
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  func (c *Config) String() string {
   116  	return fmt.Sprintf(
   117  		"Version: %d\nSmall URL: %s\nLarge URL: %s\nUpload URL: %s\nEndpoint: %s\n",
   118  		c.Version,
   119  		c.Urls.SmallUrl,
   120  		c.Urls.LargeUrl,
   121  		c.Urls.UploadUrl,
   122  		c.ConnectToAddr,
   123  	)
   124  }
   125  
   126  func (c *Config) IsValid() error {
   127  	if parsedUrl, err := url.ParseRequestURI(c.Urls.LargeUrl); err != nil ||
   128  		parsedUrl.Scheme != "https" {
   129  		return fmt.Errorf(
   130  			"configuration url large_https_download_url is invalid: %s",
   131  			utilities.Conditional(
   132  				len(c.Urls.LargeUrl) != 0,
   133  				c.Urls.LargeUrl,
   134  				"Missing",
   135  			),
   136  		)
   137  	}
   138  	if parsedUrl, err := url.ParseRequestURI(c.Urls.SmallUrl); err != nil ||
   139  		parsedUrl.Scheme != "https" {
   140  		return fmt.Errorf(
   141  			"configuration url small_https_download_url is invalid: %s",
   142  			utilities.Conditional(
   143  				len(c.Urls.SmallUrl) != 0,
   144  				c.Urls.SmallUrl,
   145  				"Missing",
   146  			),
   147  		)
   148  	}
   149  	if parsedUrl, err := url.ParseRequestURI(c.Urls.UploadUrl); err != nil ||
   150  		parsedUrl.Scheme != "https" {
   151  		return fmt.Errorf(
   152  			"configuration url https_upload_url is invalid: %s",
   153  			utilities.Conditional(
   154  				len(c.Urls.UploadUrl) != 0,
   155  				c.Urls.UploadUrl,
   156  				"Missing",
   157  			),
   158  		)
   159  	}
   160  	return nil
   161  }