Exemplo n.º 1
0
  private static void recordFetchTelemetry(final Exception exception) {
    if (exception == null) {
      // Should never happen.
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
      return;
    }

    if (exception instanceof UnknownHostException) {
      // Unknown host => we're offline.
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_OFFLINE);
      return;
    }

    if (exception instanceof SSLException) {
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SSL_ERROR);
      return;
    }

    if (exception instanceof ProtocolException || exception instanceof SocketException) {
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SOCKET_ERROR);
      return;
    }

    Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
  }
Exemplo n.º 2
0
  /**
   * Fetch the provided URI, returning a {@link JarInputStream} if the response body is appropriate.
   *
   * <p>Protected to allow for mocking.
   *
   * @return the entity body as a stream, or null on failure.
   */
  @SuppressWarnings("static-method")
  @RobocopTarget
  protected JarInputStream fetchDistribution(URI uri, HttpURLConnection connection)
      throws IOException {
    final int status = connection.getResponseCode();

    Log.d(LOGTAG, "Distribution fetch: " + status);
    // We record HTTP statuses as 2xx, 3xx, 4xx, 5xx => 2, 3, 4, 5.
    final int value;
    if (status > 599 || status < 100) {
      Log.wtf(LOGTAG, "Unexpected HTTP status code: " + status);
      value = CODE_CATEGORY_STATUS_OUT_OF_RANGE;
    } else {
      value = status / 100;
    }

    Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, value);

    if (status != 200) {
      Log.w(LOGTAG, "Got status " + status + " fetching distribution.");
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_NON_SUCCESS_RESPONSE);
      return null;
    }

    final String contentType = connection.getContentType();
    if (contentType == null || !contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
      Log.w(LOGTAG, "Malformed response: invalid Content-Type.");
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE);
      return null;
    }

    return new JarInputStream(new BufferedInputStream(connection.getInputStream()), true);
  }
Exemplo n.º 3
0
  /**
   * Get the Android preferences from the preferences.json file, if any exist.
   *
   * @return The preferences in a JSONObject, or an empty JSONObject if no preferences are defined.
   */
  public JSONObject getAndroidPreferences() {
    final File descFile = getDistributionFile("preferences.json");
    if (descFile == null) {
      // Logging and existence checks are handled in getDistributionFile.
      return new JSONObject();
    }

    try {
      final JSONObject all = FileUtils.readJSONObjectFromFile(descFile);

      if (!all.has("AndroidPreferences")) {
        return new JSONObject();
      }

      return all.getJSONObject("AndroidPreferences");

    } catch (IOException e) {
      Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return new JSONObject();
    } catch (JSONException e) {
      Log.e(LOGTAG, "Error parsing preferences.json", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return new JSONObject();
    }
  }
Exemplo n.º 4
0
  public DistributionDescriptor getDescriptor() {
    File descFile = getDistributionFile("preferences.json");
    if (descFile == null) {
      // Logging and existence checks are handled in getDistributionFile.
      return null;
    }

    try {
      JSONObject all = FileUtils.readJSONObjectFromFile(descFile);

      if (!all.has("Global")) {
        Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
        return null;
      }

      return new DistributionDescriptor(all.getJSONObject("Global"));

    } catch (IOException e) {
      Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return null;
    } catch (JSONException e) {
      Log.e(LOGTAG, "Error parsing preferences.json", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return null;
    }
  }
Exemplo n.º 5
0
  public JSONArray getBookmarks() {
    File bookmarks = getDistributionFile("bookmarks.json");
    if (bookmarks == null) {
      // Logging and existence checks are handled in getDistributionFile.
      return null;
    }

    try {
      return new JSONArray(FileUtils.readStringFromFile(bookmarks));
    } catch (IOException e) {
      Log.e(LOGTAG, "Error getting bookmarks", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return null;
    } catch (JSONException e) {
      Log.e(LOGTAG, "Error parsing bookmarks.json", e);
      Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
      return null;
    }
  }
Exemplo n.º 6
0
  private URI getReferredDistribution(ReferrerDescriptor descriptor) {
    final String content = descriptor.content;
    if (content == null) {
      return null;
    }

    // We restrict here to avoid injection attacks. After all,
    // we're downloading a distribution payload based on intent input.
    if (!content.matches("^[a-zA-Z0-9]+$")) {
      Log.e(LOGTAG, "Invalid referrer content: " + content);
      Telemetry.addToHistogram(HISTOGRAM_REFERRER_INVALID, 1);
      return null;
    }

    try {
      return new URI(FETCH_PROTOCOL, FETCH_HOSTNAME, FETCH_PATH + content + FETCH_EXTENSION, null);
    } catch (URISyntaxException e) {
      // This should never occur.
      Log.wtf(LOGTAG, "Invalid URI with content " + content + "!");
      return null;
    }
  }
Exemplo n.º 7
0
  /**
   * If applicable, download and select the distribution specified in the referrer intent.
   *
   * @return true if a referrer-supplied distribution was selected.
   */
  private boolean checkIntentDistribution(final ReferrerDescriptor referrer) {
    if (referrer == null) {
      return false;
    }

    URI uri = getReferredDistribution(referrer);
    if (uri == null) {
      return false;
    }

    long start = SystemClock.uptimeMillis();
    Log.v(LOGTAG, "Downloading referred distribution: " + uri);

    try {
      final HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();

      // If the Search Activity starts, and we handle the referrer intent, this'll return
      // null. Recover gracefully in this case.
      final GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface();
      final String ua;
      if (geckoInterface == null) {
        // Fall back to GeckoApp's default implementation.
        ua =
            HardwareUtils.isTablet()
                ? AppConstants.USER_AGENT_FENNEC_TABLET
                : AppConstants.USER_AGENT_FENNEC_MOBILE;
      } else {
        ua = geckoInterface.getDefaultUAString();
      }

      connection.setRequestProperty(HTTP.USER_AGENT, ua);
      connection.setRequestProperty("Accept", EXPECTED_CONTENT_TYPE);

      try {
        final JarInputStream distro;
        try {
          distro = fetchDistribution(uri, connection);
        } catch (Exception e) {
          Log.e(LOGTAG, "Error fetching distribution from network.", e);
          recordFetchTelemetry(e);
          return false;
        }

        long end = SystemClock.uptimeMillis();
        final long duration = end - start;
        Log.d(LOGTAG, "Distro fetch took " + duration + "ms; result? " + (distro != null));
        Telemetry.addToHistogram(
            HISTOGRAM_DOWNLOAD_TIME_MS, clamp(MAX_DOWNLOAD_TIME_MSEC, duration));

        if (distro == null) {
          // Nothing to do.
          return false;
        }

        // Try to copy distribution files from the fetched stream.
        try {
          Log.d(LOGTAG, "Copying files from fetched zip.");
          if (copyFilesFromStream(distro)) {
            // We always copy to the data dir, and we only copy files from
            // a 'distribution' subdirectory. Now determine our actual distribution directory.
            return checkDataDistribution();
          }
        } catch (SecurityException e) {
          Log.e(LOGTAG, "Security exception copying files. Corrupt or malicious?", e);
          Telemetry.addToHistogram(
              HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_SECURITY_EXCEPTION);
        } catch (Exception e) {
          Log.e(LOGTAG, "Error copying files from distribution.", e);
          Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_EXCEPTION);
        } finally {
          distro.close();
        }
      } finally {
        connection.disconnect();
      }
    } catch (IOException e) {
      Log.e(LOGTAG, "Error copying distribution files from network.", e);
      recordFetchTelemetry(e);
    }

    return false;
  }