github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/parameters/transferURLs.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package parameters
    21  
    22  import (
    23  	"encoding/base64"
    24  
    25  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    26  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
    27  )
    28  
    29  // TransferURL specifies a URL for uploading or downloading resources along
    30  // with parameters for the transfer strategy.
    31  type TransferURL struct {
    32  
    33  	// URL is the location of the resource. This string is slightly obfuscated
    34  	// with base64 encoding to mitigate trivial binary executable string scanning.
    35  	URL string
    36  
    37  	// SkipVerify indicates whether to verify HTTPS certificates. In some
    38  	// circumvention scenarios, verification is not possible. This must
    39  	// only be set to true when the resource has its own verification mechanism.
    40  	SkipVerify bool
    41  
    42  	// OnlyAfterAttempts specifies how to schedule this URL when transferring
    43  	// the same resource (same entity, same ETag) from multiple different
    44  	// candidate locations. For a value of N, this URL is only a candidate
    45  	// after N rounds of attempting the transfer to or from other URLs.
    46  	OnlyAfterAttempts int
    47  
    48  	// B64EncodedPublicKey is a base64-encoded RSA public key to be used for
    49  	// encrypting the resource, when uploading, or for verifying a signature of
    50  	// the resource, when downloading. Required by some operations, such as
    51  	// uploading feedback.
    52  	B64EncodedPublicKey string `json:",omitempty"`
    53  
    54  	// RequestHeaders are optional HTTP headers to set on any requests made to
    55  	// the destination.
    56  	RequestHeaders map[string]string `json:",omitempty"`
    57  }
    58  
    59  // TransferURLs is a list of transfer URLs.
    60  type TransferURLs []*TransferURL
    61  
    62  // DecodeAndValidate validates a list of transfer URLs.
    63  //
    64  // At least one TransferURL in the list must have OnlyAfterAttempts of 0,
    65  // or no TransferURL would be selected on the first attempt.
    66  func (t TransferURLs) DecodeAndValidate() error {
    67  
    68  	hasOnlyAfterZero := false
    69  	for _, transferURL := range t {
    70  		if transferURL.OnlyAfterAttempts == 0 {
    71  			hasOnlyAfterZero = true
    72  		}
    73  		decodedURL, err := base64.StdEncoding.DecodeString(transferURL.URL)
    74  		if err != nil {
    75  			return errors.Tracef("failed to decode URL: %s", err)
    76  		}
    77  
    78  		transferURL.URL = string(decodedURL)
    79  	}
    80  
    81  	if !hasOnlyAfterZero {
    82  		return errors.Tracef("must be at least one TransferURL with OnlyAfterAttempts = 0")
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  // CanonicalURL returns the canonical URL, to be used as a key when storing
    89  // information related to the TransferURLs, such as an ETag.
    90  func (t TransferURLs) CanonicalURL() string {
    91  
    92  	// The first OnlyAfterAttempts = 0 URL is the canonical URL. This
    93  	// is the value used as the key for SetUrlETag when multiple download
    94  	// URLs can be used to fetch a single entity.
    95  
    96  	for _, transferURL := range t {
    97  		if transferURL.OnlyAfterAttempts == 0 {
    98  			return transferURL.URL
    99  		}
   100  	}
   101  
   102  	return ""
   103  }
   104  
   105  // Select chooses a TransferURL from the list.
   106  //
   107  // The TransferURL is selected based at random from the candidates allowed in
   108  // the specified attempt.
   109  func (t TransferURLs) Select(attempt int) *TransferURL {
   110  
   111  	candidates := make([]int, 0)
   112  	for index, URL := range t {
   113  		if attempt >= URL.OnlyAfterAttempts {
   114  			candidates = append(candidates, index)
   115  		}
   116  	}
   117  
   118  	if len(candidates) < 1 {
   119  		// This case is not expected, as DecodeAndValidate should reject configs
   120  		// that would have no candidates for 0 attempts.
   121  		return nil
   122  	}
   123  
   124  	selection := prng.Intn(len(candidates))
   125  	transferURL := t[candidates[selection]]
   126  
   127  	return transferURL
   128  }