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 }