about summary refs log tree commit diff
path: root/pkgs/applications/networking/browsers/chromium/source/update.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/applications/networking/browsers/chromium/source/update.nix')
-rw-r--r--pkgs/applications/networking/browsers/chromium/source/update.nix282
1 files changed, 195 insertions, 87 deletions
diff --git a/pkgs/applications/networking/browsers/chromium/source/update.nix b/pkgs/applications/networking/browsers/chromium/source/update.nix
index 49864cf1c7b0d..d4dc3b59cbc76 100644
--- a/pkgs/applications/networking/browsers/chromium/source/update.nix
+++ b/pkgs/applications/networking/browsers/chromium/source/update.nix
@@ -3,17 +3,24 @@
 let
   inherit (import ../../../../../../. {
     inherit system;
-  }) lib writeText stdenv;
+  }) lib runCommand writeText stdenv curl cacert nix;
 
   sources = if builtins.pathExists ./sources.nix
             then import ./sources.nix
-            else null;
+            else {};
 
   bucketURL = "https://commondatastorage.googleapis.com/"
             + "chromium-browser-official";
 
+  mkVerURL = version: "${bucketURL}/chromium-${version}.tar.xz";
+
   debURL = "https://dl.google.com/linux/chrome/deb/pool/main/g";
 
+  getDebURL = channelName: version: arch: mirror: let
+    packageSuffix = if channelName == "dev" then "unstable" else channelName;
+    packageName = "google-chrome-${packageSuffix}";
+  in "${mirror}/${packageName}/${packageName}_${version}-1_${arch}.deb";
+
   # Untrusted mirrors, don't try to update from them!
   debMirrors = [
     "http://95.31.35.30/chrome/pool/main/g"
@@ -21,32 +28,6 @@ let
     "http://repo.fdzh.org/chrome/deb/pool/main/g"
   ];
 
-  tryChannel = channel: let
-    chan = builtins.getAttr channel sources;
-  in if sources != null then ''
-    oldver="${chan.version}";
-    echo -n "Checking if $oldver ($channel) is up to date..." >&2;
-    if [ "x$(get_newest_ver "$version" "$oldver")" != "x$oldver" ];
-    then
-      echo " no, getting sha256 for new version $version:" >&2;
-      sha256="$(prefetch_sha "$channel" "$version")" || return 1;
-    else
-      echo " yes, keeping old sha256." >&2;
-      sha256="${chan.sha256}";
-      ${if (chan ? sha256bin32 && chan ? sha256bin64) then ''
-        sha256="$sha256.${chan.sha256bin32}.${chan.sha256bin64}";
-      '' else ''
-        sha256="$sha256.$(prefetch_deb_sha "$channel" "$version")";
-      ''}
-    fi;
-  '' else ''
-    sha256="$(prefetch_sha "$channel" "$version")" || return 1;
-  '';
-
-  caseChannel = channel: ''
-    ${channel}) ${tryChannel channel};;
-  '';
-
 in rec {
   getChannel = channel: let
     chanAttrs = builtins.getAttr channel sources;
@@ -54,70 +35,197 @@ in rec {
     inherit (chanAttrs) version;
 
     main = {
-      url = "${bucketURL}/chromium-${chanAttrs.version}.tar.xz";
+      url = mkVerURL chanAttrs.version;
       inherit (chanAttrs) sha256;
     };
 
     binary = let
-      pname = if channel == "dev"
-              then "google-chrome-unstable"
-              else "google-chrome-${channel}";
-      arch = if stdenv.is64bit then "amd64" else "i386";
-      relpath = "${pname}/${pname}_${chanAttrs.version}-1_${arch}.deb";
-    in lib.optionalAttrs (chanAttrs ? sha256bin64) {
-      urls = map (url: "${url}/${relpath}") ([ debURL ] ++ debMirrors);
-      sha256 = if stdenv.is64bit
-               then chanAttrs.sha256bin64
-               else chanAttrs.sha256bin32;
-    };
+      mkUrls = arch: let
+        mkURLForMirror = getDebURL channel chanAttrs.version arch;
+      in map mkURLForMirror ([ debURL ] ++ debMirrors);
+    in if stdenv.is64bit && chanAttrs ? sha256bin64 then {
+      urls = mkUrls "amd64";
+      sha256 = chanAttrs.sha256bin64;
+    } else if !stdenv.is64bit && chanAttrs ? sha256bin32 then {
+      urls = mkUrls "i386";
+      sha256 = chanAttrs.sha256bin32;
+    } else throw "No Chrome plugins are available for your architecture.";
   };
 
-  updateHelpers = writeText "update-helpers.sh" ''
-
-    prefetch_main_sha()
-    {
-      nix-prefetch-url "${bucketURL}/chromium-$2.tar.xz";
-    }
-
-    prefetch_deb_sha()
-    {
-      channel="$1";
-      version="$2";
-
-      case "$1" in
-        dev) pname="google-chrome-unstable";;
-        *)   pname="google-chrome-$channel";;
-      esac;
-
-      deb_pre="${debURL}/$pname/$pname";
-
-      deb32=$(nix-prefetch-url "''${deb_pre}_$version-1_i386.deb");
-      deb64=$(nix-prefetch-url "''${deb_pre}_$version-1_amd64.deb");
-
-      echo "$deb32.$deb64";
-      return 0;
-    }
-
-    prefetch_sha()
-    {
-      main_sha="$(prefetch_main_sha "$@")" || return 1;
-      deb_sha="$(prefetch_deb_sha "$@")" || return 1;
-      echo "$main_sha.$deb_sha";
-      return 0;
-    }
-
-    get_sha256()
-    {
-      channel="$1";
-      version="$2";
-
-      case "$channel" in
-        ${lib.concatMapStrings caseChannel [ "stable" "dev" "beta" ]}
-      esac;
-
-      sha_insert "$version" "$sha256";
-      echo "$sha256";
-      return 0;
-    }
+  update = let
+    csv2nix = name: src: import (runCommand "${name}.nix" {
+      src = builtins.fetchurl src;
+    } ''
+      esc() { echo "\"$(echo "$1" | sed -e 's/"\\$/\\&/')\""; }
+      IFS=, read -r -a headings <<< "$(head -n1 "$src")"
+      echo "[" > "$out"
+      tail -n +2 "$src" | while IFS=, read -r -a line; do
+        echo "  {"
+        for idx in "''${!headings[@]}"; do
+          echo "    $(esc "''${headings[idx]}") = $(esc ''${line[$idx]});"
+        done
+        echo "  }"
+      done >> "$out"
+      echo "]" >> "$out"
+    '');
+
+    channels = lib.fold lib.recursiveUpdate {} (map (attrs: {
+      ${attrs.os}.${attrs.channel} = attrs // {
+        history = let
+          drvName = "omahaproxy-${attrs.os}.${attrs.channel}-info";
+          history = csv2nix drvName "http://omahaproxy.appspot.com/history";
+          cond = h: attrs.os == h.os && attrs.channel == h.channel
+                 && lib.versionOlder h.version attrs.current_version;
+          # Note that this is a *reverse* sort!
+          sorter = a: b: lib.versionOlder b.version a.version;
+          sorted = builtins.sort sorter (lib.filter cond history);
+        in map (lib.flip removeAttrs ["os" "channel"]) sorted;
+        version = attrs.current_version;
+      };
+    }) (csv2nix "omahaproxy-info" "http://omahaproxy.appspot.com/all?csv=1"));
+
+    /*
+      XXX: This is essentially the same as:
+
+        builtins.tryEval (builtins.fetchurl url)
+
+      ... except that tryEval on fetchurl isn't working and doesn't catch errors
+      for fetchurl, so we go for a different approach.
+
+      We only have fixed-output derivations that can have networking access, so
+      we abuse MD5 and its weaknesses to forge a fixed-output derivation which
+      is not so fixed, because it emits different contents that have the same
+      MD5 hash.
+
+      Using this method, we can distinguish whether the URL is available or
+      whether it's not based on the actual content.
+
+      So let's use tryEval as soon as it's working with fetchurl in Nix.
+    */
+    tryFetch = url: let
+      mkBin = b: runCommand "binary-blurb" { inherit b; } ''
+        h="$(echo "$b" | sed -e ':r;N;$!br;s/[^ \n][^ \n]/\\x&/g;s/[ \n]//g')"
+        echo -ne "$h" > "$out"
+      '';
+
+      # Both MD5 hash collision examples are from:
+      # https://en.wikipedia.org/wiki/MD5#Collision_vulnerabilities
+      hashCollTrue = mkBin ''
+        d131dd02c5e6eec4 693d9a0698aff95c 2fcab58712467eab 4004583eb8fb7f89
+        55ad340609f4b302 83e488832571415a 085125e8f7cdc99f d91dbdf280373c5b
+        d8823e3156348f5b ae6dacd436c919c6 dd53e2b487da03fd 02396306d248cda0
+        e99f33420f577ee8 ce54b67080a80d1e c69821bcb6a88393 96f9652b6ff72a70
+      '';
+
+      hashCollFalse = mkBin ''
+        d131dd02c5e6eec4 693d9a0698aff95c 2fcab50712467eab 4004583eb8fb7f89
+        55ad340609f4b302 83e4888325f1415a 085125e8f7cdc99f d91dbd7280373c5b
+        d8823e3156348f5b ae6dacd436c919c6 dd53e23487da03fd 02396306d248cda0
+        e99f33420f577ee8 ce54b67080280d1e c69821bcb6a88393 96f965ab6ff72a70
+      '';
+
+      cacheVal = let
+        urlHash = builtins.hashString "sha256" url;
+        timeSlice = builtins.currentTime / 600;
+      in "${urlHash}-${toString timeSlice}";
+
+      successBin = stdenv.mkDerivation {
+        name = "tryfetch-${cacheVal}";
+        inherit url;
+
+        outputHash = "79054025255fb1a26e4bc422aef54eb4";
+        outputHashMode = "flat";
+        outputHashAlgo = "md5";
+
+        buildInputs = [ curl ];
+        preferLocalBuild = true;
+
+        buildCommand = ''
+          if SSL_CERT_FILE="${cacert}/etc/ssl/certs/ca-bundle.crt" \
+             curl -s -L -f -I "$url" > /dev/null; then
+            cat "${hashCollTrue}" > "$out"
+          else
+            cat "${hashCollFalse}" > "$out"
+          fi
+        '';
+
+        impureEnvVars = [
+          "http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy"
+        ];
+      };
+
+    in {
+      success = builtins.readFile successBin == builtins.readFile hashCollTrue;
+      value = builtins.fetchurl url;
+    };
+
+    fetchLatest = channel: let
+      result = tryFetch (mkVerURL channel.version);
+    in if result.success then result.value else fetchLatest (channel // {
+      version = if channel.history != []
+                then (lib.head channel.history).version
+                else throw "Unfortunately there's no older version than " +
+                           "${channel.version} available for channel " +
+                           "${channel.channel} on ${channel.os}.";
+      history = lib.tail channel.history;
+    });
+
+    getHash = path: import (runCommand "gethash.nix" {
+      inherit path;
+      buildInputs = [ nix ];
+    } ''
+      sha256="$(nix-hash --flat --base32 --type sha256 "$path")"
+      echo "\"$sha256\"" > "$out"
+    '');
+
+    isLatest = channel: version: let
+      ourVersion = sources.${channel}.version or null;
+    in if ourVersion == null then false
+       else lib.versionOlder version sources.${channel}.version
+         || version == sources.${channel}.version;
+
+    # We only support GNU/Linux right now.
+    linuxChannels = let
+      genLatest = channelName: channel: let
+        newUpstream = {
+          inherit (channel) version;
+          sha256 = getHash (fetchLatest channel);
+        };
+        keepOld = let
+          oldChannel = sources.${channelName};
+        in {
+          inherit (oldChannel) version sha256;
+        } // lib.optionalAttrs (oldChannel ? sha256bin32) {
+          inherit (oldChannel) sha256bin32;
+        } // lib.optionalAttrs (oldChannel ? sha256bin64) {
+          inherit (oldChannel) sha256bin64;
+        };
+      in if isLatest channelName channel.version then keepOld else newUpstream;
+    in lib.mapAttrs genLatest channels.linux;
+
+    getLinuxFlash = channelName: channel: let
+      inherit (channel) version;
+      fetchArch = arch: tryFetch (getDebURL channelName version arch debURL);
+      packages = lib.genAttrs ["i386" "amd64"] fetchArch;
+      isNew = arch: attr: !(builtins.hasAttr attr channel)
+                       && packages.${arch}.success;
+    in channel // lib.optionalAttrs (isNew "i386" "sha256bin32") {
+      sha256bin32 = getHash (packages.i386.value);
+    } // lib.optionalAttrs (isNew "amd64" "sha256bin64") {
+      sha256bin64 = getHash (packages.amd64.value);
+    };
+
+    newChannels = lib.mapAttrs getLinuxFlash linuxChannels;
+
+    dumpAttrs = indent: attrs: let
+      mkVal = val: if lib.isAttrs val then dumpAttrs (indent + 1) val
+                   else "\"${lib.escape ["$" "\\" "\""] (toString val)}\"";
+      mkIndent = level: lib.concatStrings (builtins.genList (_: "  ") level);
+      mkAttr = key: val: "${mkIndent (indent + 1)}${key} = ${mkVal val};\n";
+      attrLines = lib.mapAttrsToList mkAttr attrs;
+    in "{\n" + (lib.concatStrings attrLines) + (mkIndent indent) + "}";
+  in writeText "chromium-new-sources.nix" ''
+    # This file is autogenerated from update.sh in the parent directory.
+    ${dumpAttrs 0 newChannels}
   '';
 }