[tor-commits] [sandboxed-tor-browser/master] Bug 20778: Refactor the update logic to prepare for background updates.

yawning at torproject.org yawning at torproject.org
Wed Dec 28 04:44:16 UTC 2016


commit e5d543b50b146835783f27ccad5ca2beea6b6ac1
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Thu Dec 22 19:57:27 2016 +0000

    Bug 20778: Refactor the update logic to prepare for background updates.
    
    This refactors the update logic, and changes the config file flag
    signalling an update is required to a bool from a unix timestamp.
    
    Note: This breaks the update functionality since updates are never
    triggered, but is required for the background check refactor.
---
 .../internal/ui/config/config.go                   |  22 +--
 .../sandboxed-tor-browser/internal/ui/gtk/ui.go    |   2 +-
 .../sandboxed-tor-browser/internal/ui/install.go   | 155 +---------------
 .../sandboxed-tor-browser/internal/ui/update.go    | 205 +++++++++++++++++++++
 4 files changed, 214 insertions(+), 170 deletions(-)

diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
index ff5ef9e..a1af1cd 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/config/config.go
@@ -25,7 +25,6 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
-	"time"
 
 	butils "git.schwanenlied.me/yawning/bulb.git/utils"
 	xdg "github.com/cep21/xdgbasedir"
@@ -300,9 +299,8 @@ type Config struct {
 	// Locale is the Tor Browser locale to install ("en-US", "ja").
 	Locale string `json:"locale,omitempty"`
 
-	// LastUpdateCheck is the UNIX time when the last update check was
-	// sucessfully completed.
-	LastUpdateCheck int64 `json:"lastUpdateCheck,omitEmpty"`
+	// ForceUpdate is set if the installed bundle is known to be obsolete.
+	ForceUpdate bool `json:"forceUpdate"`
 
 	// Tor is the Tor network configuration.
 	Tor Tor `json:"tor,omitEmpty"`
@@ -374,19 +372,11 @@ func (cfg *Config) SetFirstLaunch(b bool) {
 	}
 }
 
-// NeedsUpdateCheck returns true if the bundle needs to be checked for updates,
-// and possibly updated.
-func (cfg *Config) NeedsUpdateCheck() bool {
-	const updateInterval = 60 * 60 * 12 // 12 hours.
-	now := time.Now().Unix()
-	return (now > cfg.LastUpdateCheck+updateInterval) || cfg.LastUpdateCheck > now
-}
-
-// SetLastUpdateCheck sets the last update check time and marks the config
+// SetForceUpdate sets the bundle as needed an update and marks the config
 // dirty.
-func (cfg *Config) SetLastUpdateCheck(t int64) {
-	if cfg.LastUpdateCheck != t {
-		cfg.LastUpdateCheck = t
+func (cfg *Config) SetForceUpdate(b bool) {
+	if cfg.ForceUpdate != b {
+		cfg.ForceUpdate = b
 		cfg.isDirty = true
 	}
 }
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
index 161b32f..4e526bb 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/gtk/ui.go
@@ -159,7 +159,7 @@ func (ui *gtkUI) onDestroy() {
 
 func (ui *gtkUI) launch() error {
 	// If we don't need to update, and would just launch, quash the UI.
-	checkUpdate := ui.Cfg.NeedsUpdateCheck()
+	checkUpdate := ui.Cfg.ForceUpdate
 	squelchUI := !checkUpdate && ui.Cfg.UseSystemTor
 
 	async := async.NewAsync()
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/install.go b/src/cmd/sandboxed-tor-browser/internal/ui/install.go
index 0c9e80e..ac9f246 100644
--- a/src/cmd/sandboxed-tor-browser/internal/ui/install.go
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/install.go
@@ -1,4 +1,4 @@
-// install.go - Install/Update logic.
+// install.go - Install logic.
 // Copyright (C) 2016  Yawning Angel.
 //
 // This program is free software: you can redistribute it and/or modify
@@ -17,20 +17,15 @@
 package ui
 
 import (
-	"bytes"
-	"crypto/sha512"
-	"encoding/hex"
 	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
 	"runtime"
-	"time"
 
 	"cmd/sandboxed-tor-browser/internal/data"
 	"cmd/sandboxed-tor-browser/internal/installer"
-	"cmd/sandboxed-tor-browser/internal/sandbox"
 	. "cmd/sandboxed-tor-browser/internal/ui/async"
 	"cmd/sandboxed-tor-browser/internal/ui/config"
 	"cmd/sandboxed-tor-browser/internal/utils"
@@ -90,7 +85,6 @@ func (c *Common) DoInstall(async *Async) {
 			return
 		}
 	}
-	checkAt := time.Now().Unix()
 
 	log.Printf("install: Version: %v Downloads: %v", version, downloads)
 
@@ -149,7 +143,7 @@ func (c *Common) DoInstall(async *Async) {
 	}
 
 	// Set the appropriate bits in the config.
-	c.Cfg.SetLastUpdateCheck(checkAt)
+	c.Cfg.SetForceUpdate(false)
 	c.Cfg.SetFirstLaunch(true)
 
 	// Sync the config, and return.
@@ -173,148 +167,3 @@ func writeAutoconfig(cfg *config.Config) error {
 
 	return nil
 }
-
-func (c *Common) doUpdate(async *Async, dialFn dialFunc) {
-	// This attempts to follow the process that Firefox uses to check for
-	// updates.  https://wiki.mozilla.org/Software_Update:Checking_For_Updates
-
-	// Check for updates.
-	log.Printf("launch: Checking for updates.")
-	async.UpdateProgress("Checking for updates.")
-
-	// Create the async HTTP client.
-	client := newHPKPGrabClient(dialFn)
-
-	// Check the version, by downloading the XML file.
-	// XXX: Fall back to https over clearnet if the onion fails.
-	var update *installer.UpdateEntry
-	if url, err := installer.UpdateURL(c.Manif, true); err != nil {
-		async.Err = err
-		return
-	} else {
-		log.Printf("launch: Update URL: %v", url)
-		if b := async.Grab(client, url, nil); async.Err != nil {
-			return
-		} else if update, async.Err = installer.GetUpdateEntry(b); async.Err != nil {
-			return
-		}
-	}
-
-	checkAt := time.Now().Unix()
-	if update == nil {
-		log.Printf("launch: Installed bundle is current.")
-
-		// Save the time that the update check was done.
-		c.Cfg.SetLastUpdateCheck(checkAt)
-		async.Err = c.Cfg.Sync()
-		return
-	}
-
-	// Force an update check again if the user exits for any reason, since
-	// we know there is an update available.
-	c.Cfg.SetLastUpdateCheck(0)
-	if async.Err = c.Cfg.Sync(); async.Err != nil {
-		return
-	}
-
-	// Ensure that the update entry version is actually neweer.
-	if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
-		log.Printf("launch: Update server provided a downgrade: '%v'", update.AppVersion)
-		async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion)
-		return
-	}
-
-	// Figure out the best MAR to download.
-	patches := make(map[string]*installer.Patch)
-	for _, v := range update.Patch {
-		if patches[v.Type] != nil {
-			async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type)
-			return
-		}
-		patches[v.Type] = &v
-	}
-	patch := patches["partial"] // Favor the delta update mechanism.
-	if patch == nil {
-		if patch = patches["complete"]; patch == nil {
-			async.Err = fmt.Errorf("no suitable MAR file found")
-			return
-		}
-	}
-
-	// Download the MAR file.
-	log.Printf("update: Downloading %v", patch.Url)
-	async.UpdateProgress("Downloading Tor Browser Update.")
-
-	var mar []byte
-	if mar = async.Grab(client, patch.Url, func(s string) { async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); async.Err != nil {
-		return
-	}
-
-	log.Printf("update: Validating Tor Browser Update.")
-	async.UpdateProgress("Validating Tor Browser Update.")
-
-	// Validate the hash against that listed in the XML file.
-	expectedHash, err := hex.DecodeString(patch.HashValue)
-	if err != nil {
-		async.Err = fmt.Errorf("failed to decode HashValue: %v", err)
-		return
-	}
-	switch patch.HashFunction {
-	case "SHA512":
-		derivedHash := sha512.Sum512(mar)
-		if !bytes.Equal(expectedHash, derivedHash[:]) {
-			async.Err = fmt.Errorf("downloaded hash does not match patch metadata")
-			return
-		}
-	default:
-		async.Err = fmt.Errorf("unsupported hash function: '%v'", patch.HashFunction)
-		return
-	}
-
-	// ... and verify the signature block in the MAR with our copy of the key.
-	if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil {
-		return
-	}
-
-	// Shutdown the old tor now.
-	if c.tor != nil {
-		log.Printf("update: Shutting down old tor.")
-		c.tor.Shutdown()
-		c.tor = nil
-	}
-
-	// Apply the update.
-	log.Printf("update: Updating Tor Browser.")
-	async.UpdateProgress("Updating Tor Browser.")
-
-	async.ToUI <- false //  Lock out canceling.
-
-	if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil {
-		return
-	}
-
-	// Reinstall the autoconfig stuff.
-	if async.Err = writeAutoconfig(c.Cfg); async.Err != nil {
-		return
-	}
-
-	// Update the maniftest and config.
-	c.Manif.SetVersion(update.AppVersion)
-	if async.Err = c.Manif.Sync(); async.Err != nil {
-		return
-	}
-	c.Cfg.SetLastUpdateCheck(checkAt)
-	if async.Err = c.Cfg.Sync(); async.Err != nil {
-		return
-	}
-
-	async.ToUI <- true // Unlock canceling.
-
-	// Restart tor if we launched it.
-	if !c.Cfg.UseSystemTor {
-		log.Printf("launch: Reconnecting to the Tor network.")
-		async.UpdateProgress("Reconnecting to the Tor network.")
-		_, async.Err = c.launchTor(async, false)
-	}
-	return
-}
diff --git a/src/cmd/sandboxed-tor-browser/internal/ui/update.go b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
new file mode 100644
index 0000000..7c5f812
--- /dev/null
+++ b/src/cmd/sandboxed-tor-browser/internal/ui/update.go
@@ -0,0 +1,205 @@
+// update.go - Update logic.
+// Copyright (C) 2016  Yawning Angel.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package ui
+
+import (
+	"bytes"
+	"crypto/sha512"
+	"encoding/hex"
+	"fmt"
+	"log"
+
+	"cmd/sandboxed-tor-browser/internal/installer"
+	"cmd/sandboxed-tor-browser/internal/sandbox"
+	. "cmd/sandboxed-tor-browser/internal/ui/async"
+)
+
+func (c *Common) CheckUpdate(async *Async, dialFn dialFunc) *installer.UpdateEntry {
+	// Check for updates.
+	log.Printf("update: Checking for updates.")
+	async.UpdateProgress("Checking for updates.")
+
+	// Create the async HTTP client.
+	client := newHPKPGrabClient(dialFn)
+
+	// Determine where the update metadata should be fetched from.
+	updateURLs := []string{}
+	for _, b := range []bool{true, false} { // Prioritize .onions.
+		if url, err := installer.UpdateURL(c.Manif, b); err != nil {
+			log.Printf("update: Failed to get update URL (onion: %v): %v", b, err)
+		} else {
+			updateURLs = append(updateURLs, url)
+		}
+	}
+	if len(updateURLs) == 0 {
+		log.Printf("update: Failed to find any update URLs")
+		async.Err = fmt.Errorf("failed to find any update URLs")
+		return nil
+	}
+
+	// Check the version, by downloading the XML file.
+	var update *installer.UpdateEntry
+	fetchOk := false
+	for _, url := range updateURLs {
+		log.Printf("update: Metadata URL: %v", url)
+		async.Err = nil // Clear errors per fetch.
+		if b := async.Grab(client, url, nil); async.Err != nil {
+			log.Printf("update: Metadata download failed: %v", async.Err)
+			continue
+		} else if update, async.Err = installer.GetUpdateEntry(b); async.Err != nil {
+			log.Printf("update: Metadata parse failed: %v", async.Err)
+			continue
+		}
+		fetchOk = true
+		break
+	}
+
+	if !fetchOk {
+		// This should be set from the last update attempt...
+		if async.Err == nil {
+			async.Err = fmt.Errorf("failed to download update metadata")
+		}
+		return nil
+	}
+
+	if update == nil {
+		log.Printf("update: Installed bundle is current.")
+		c.Cfg.SetForceUpdate(false)
+	} else {
+		log.Printf("update: Installed bundle needs updating.")
+		c.Cfg.SetForceUpdate(true)
+	}
+
+	if async.Err = c.Cfg.Sync(); async.Err != nil {
+		return nil
+	}
+
+	return update
+}
+
+func (c *Common) doUpdate(async *Async, dialFn dialFunc) {
+	// This attempts to follow the process that Firefox uses to check for
+	// updates.  https://wiki.mozilla.org/Software_Update:Checking_For_Updates
+
+	// Check for updates.
+	update := c.CheckUpdate(async, dialFn)
+	if async.Err != nil || update == nil {
+		return
+	}
+
+	// Ensure that the update entry version is actually neweer.
+	if !c.Manif.BundleUpdateVersionValid(update.AppVersion) {
+		log.Printf("update: Update server provided a downgrade: '%v'", update.AppVersion)
+		async.Err = fmt.Errorf("update server provided a downgrade: '%v'", update.AppVersion)
+		return
+	}
+
+	// Figure out the best MAR to download.
+	patches := make(map[string]*installer.Patch)
+	for _, v := range update.Patch {
+		if patches[v.Type] != nil {
+			async.Err = fmt.Errorf("duplicate patch entry for kind: '%v'", v.Type)
+			return
+		}
+		patches[v.Type] = &v
+	}
+	patch := patches["partial"] // Favor the delta update mechanism.
+	if patch == nil {
+		if patch = patches["complete"]; patch == nil {
+			async.Err = fmt.Errorf("no suitable MAR file found")
+			return
+		}
+	}
+
+	// Download the MAR file.
+	log.Printf("update: Downloading %v", patch.Url)
+	async.UpdateProgress("Downloading Tor Browser Update.")
+
+	var mar []byte
+	client := newHPKPGrabClient(dialFn)
+	if mar = async.Grab(client, patch.Url, func(s string) { async.UpdateProgress(fmt.Sprintf("Downloading Tor Browser Update: %s", s)) }); async.Err != nil {
+		return
+	}
+
+	log.Printf("update: Validating Tor Browser Update.")
+	async.UpdateProgress("Validating Tor Browser Update.")
+
+	// Validate the hash against that listed in the XML file.
+	expectedHash, err := hex.DecodeString(patch.HashValue)
+	if err != nil {
+		async.Err = fmt.Errorf("failed to decode HashValue: %v", err)
+		return
+	}
+	switch patch.HashFunction {
+	case "SHA512":
+		derivedHash := sha512.Sum512(mar)
+		if !bytes.Equal(expectedHash, derivedHash[:]) {
+			async.Err = fmt.Errorf("downloaded hash does not match patch metadata")
+			return
+		}
+	default:
+		async.Err = fmt.Errorf("unsupported hash function: '%v'", patch.HashFunction)
+		return
+	}
+
+	// ... and verify the signature block in the MAR with our copy of the key.
+	if async.Err = installer.VerifyTorBrowserMAR(mar); async.Err != nil {
+		return
+	}
+
+	// Shutdown the old tor now.
+	if c.tor != nil {
+		log.Printf("update: Shutting down old tor.")
+		c.tor.Shutdown()
+		c.tor = nil
+	}
+
+	// Apply the update.
+	log.Printf("update: Updating Tor Browser.")
+	async.UpdateProgress("Updating Tor Browser.")
+
+	async.ToUI <- false //  Lock out canceling.
+
+	if async.Err = sandbox.RunUpdate(c.Cfg, mar); async.Err != nil {
+		return
+	}
+
+	// Reinstall the autoconfig stuff.
+	if async.Err = writeAutoconfig(c.Cfg); async.Err != nil {
+		return
+	}
+
+	// Update the maniftest and config.
+	c.Manif.SetVersion(update.AppVersion)
+	if async.Err = c.Manif.Sync(); async.Err != nil {
+		return
+	}
+	c.Cfg.SetForceUpdate(false)
+	if async.Err = c.Cfg.Sync(); async.Err != nil {
+		return
+	}
+
+	async.ToUI <- true // Unlock canceling.
+
+	// Restart tor if we launched it.
+	if !c.Cfg.UseSystemTor {
+		log.Printf("launch: Reconnecting to the Tor network.")
+		async.UpdateProgress("Reconnecting to the Tor network.")
+		_, async.Err = c.launchTor(async, false)
+	}
+	return
+}





More information about the tor-commits mailing list