downloadURLs.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. package parameters
  20. import (
  21. "encoding/base64"
  22. "fmt"
  23. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  24. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  25. )
  26. // DownloadURL specifies a URL for downloading resources along with parameters
  27. // for the download strategy.
  28. type DownloadURL struct {
  29. // URL is the location of the resource. This string is slightly obfuscated
  30. // with base64 encoding to mitigate trivial binary executable string scanning.
  31. URL string
  32. // SkipVerify indicates whether to verify HTTPS certificates. It some
  33. // circumvention scenarios, verification is not possible. This must
  34. // only be set to true when the resource has its own verification mechanism.
  35. SkipVerify bool
  36. // OnlyAfterAttempts specifies how to schedule this URL when downloading
  37. // the same resource (same entity, same ETag) from multiple different
  38. // candidate locations. For a value of N, this URL is only a candidate
  39. // after N rounds of attempting the download from other URLs.
  40. OnlyAfterAttempts int
  41. }
  42. // DownloadURLs is a list of download URLs.
  43. type DownloadURLs []*DownloadURL
  44. // DecodeAndValidate validates a list of download URLs.
  45. //
  46. // At least one DownloadURL in the list must have OnlyAfterAttempts of 0,
  47. // or no DownloadURL would be selected on the first attempt.
  48. func (d DownloadURLs) DecodeAndValidate() error {
  49. hasOnlyAfterZero := false
  50. for _, downloadURL := range d {
  51. if downloadURL.OnlyAfterAttempts == 0 {
  52. hasOnlyAfterZero = true
  53. }
  54. decodedURL, err := base64.StdEncoding.DecodeString(downloadURL.URL)
  55. if err != nil {
  56. return common.ContextError(fmt.Errorf("failed to decode URL: %s", err))
  57. }
  58. downloadURL.URL = string(decodedURL)
  59. }
  60. if !hasOnlyAfterZero {
  61. return common.ContextError(fmt.Errorf("must be at least one DownloadURL with OnlyAfterAttempts = 0"))
  62. }
  63. return nil
  64. }
  65. // Select chooses a DownloadURL from the list.
  66. //
  67. // The first return value is the canonical URL, to be used
  68. // as a key when storing information related to the DownloadURLs,
  69. // such as an ETag.
  70. //
  71. // The second return value is the chosen download URL, which is
  72. // selected based at random from the candidates allowed in the
  73. // specified attempt.
  74. func (d DownloadURLs) Select(attempt int) (string, string, bool) {
  75. // The first OnlyAfterAttempts = 0 URL is the canonical URL. This
  76. // is the value used as the key for SetUrlETag when multiple download
  77. // URLs can be used to fetch a single entity.
  78. canonicalURL := ""
  79. for _, downloadURL := range d {
  80. if downloadURL.OnlyAfterAttempts == 0 {
  81. canonicalURL = downloadURL.URL
  82. break
  83. }
  84. }
  85. candidates := make([]int, 0)
  86. for index, URL := range d {
  87. if attempt >= URL.OnlyAfterAttempts {
  88. candidates = append(candidates, index)
  89. }
  90. }
  91. if len(candidates) < 1 {
  92. // This case is not expected, as decodeAndValidateDownloadURLs
  93. // should reject configs that would have no candidates for
  94. // 0 attempts.
  95. return "", "", true
  96. }
  97. selection := prng.Intn(len(candidates))
  98. downloadURL := d[candidates[selection]]
  99. return downloadURL.URL, canonicalURL, downloadURL.SkipVerify
  100. }