chromium/chrome/browser/ash/drive/file_system_util.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/drive/file_system_util.h"

#include <stddef.h>

#include <memory>
#include <string>
#include <type_traits>
#include <vector>

#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/system/sys_info.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/managed_state.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "components/drive/drive_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_util.h"

using content::BrowserThread;

namespace drive::util {

using user_manager::User;
using user_manager::UserManager;

DriveIntegrationService* GetIntegrationServiceByProfile(Profile* profile) {
  DriveIntegrationService* service =
      DriveIntegrationServiceFactory::FindForProfile(profile);
  if (!service || !service->IsMounted()) {
    return nullptr;
  }
  return service;
}

bool IsUnderDriveMountPoint(const base::FilePath& path) {
  std::vector<base::FilePath::StringType> components = path.GetComponents();
  if (components.size() < 4) {
    return false;
  }
  if (components[0] != FILE_PATH_LITERAL("/")) {
    return false;
  }
  if (components[1] != FILE_PATH_LITERAL("media")) {
    return false;
  }
  if (components[2] != FILE_PATH_LITERAL("fuse")) {
    return false;
  }
  static const base::FilePath::CharType kPrefix[] =
      FILE_PATH_LITERAL("drivefs");
  if (components[3].compare(0, std::size(kPrefix) - 1, kPrefix) != 0) {
    return false;
  }

  return true;
}

base::FilePath GetCacheRootPath(const Profile* const profile) {
  base::FilePath cache_base_path;
  chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path);
  base::FilePath cache_root_path =
      cache_base_path.Append(ash::kDriveCacheDirname);
  static const base::FilePath::CharType kFileCacheVersionDir[] =
      FILE_PATH_LITERAL("v1");
  return cache_root_path.Append(kFileCacheVersionDir);
}

DriveAvailability CheckDriveAvailabilityForProfile(
    const Profile* const profile) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Disable Drive for non-Gaia accounts.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kDisableGaiaServices)) {
    return DriveAvailability::kNotAvailableForAccountType;
  }
  if (!ash::LoginState::IsInitialized()) {
    return DriveAvailability::kNotAvailableForUninitialisedLoginState;
  }
  // Disable Drive for incognito profiles.
  if (profile->IsOffTheRecord()) {
    return DriveAvailability::kNotAvailableInIncognito;
  }
  const User* const user = ash::ProfileHelper::Get()->GetUserByProfile(profile);
  if (!user || !user->HasGaiaAccount()) {
    return DriveAvailability::kNotAvailableForAccountType;
  }

  // Disable Drive if the flag has been passed and it is a test image.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kDisableDriveFsForTesting)) {
    base::SysInfo::CrashIfChromeOSNonTestImage();
    return DriveAvailability::kNotAvailableForTestImage;
  }

  return DriveAvailability::kAvailable;
}

bool IsDriveAvailableForProfile(const Profile* const profile) {
  return CheckDriveAvailabilityForProfile(profile) ==
         DriveAvailability::kAvailable;
}

DriveAvailability CheckDriveEnabledAndDriveAvailabilityForProfile(
    const Profile* const profile) {
  // Disable Drive if preference is set. This can happen with commandline flag
  // --disable-drive or enterprise policy, or with user settings.
  if (profile->GetPrefs()->GetBoolean(prefs::kDisableDrive)) {
    return DriveAvailability::kNotAvailableWhenDisableDrivePreferenceSet;
  }

  return CheckDriveAvailabilityForProfile(profile);
}

bool IsDriveEnabledForProfile(const Profile* const profile) {
  return CheckDriveEnabledAndDriveAvailabilityForProfile(profile) ==
         DriveAvailability::kAvailable;
}

bool IsDriveFsBulkPinningAvailable(const Profile* const profile) {
  // Check the "DriveFsBulkPinning" Chrome feature. If this feature is disabled,
  // then it probably means that the kill switch has been activated, and the
  // bulk-pinning feature should not be available.
  if (!base::FeatureList::IsEnabled(ash::features::kDriveFsBulkPinning)) {
    return false;
  }

  // Check the "drivefs.bulk_pinning.visible" boolean pref. If this pref is
  // false, then it probably means that it has been turned down by an enterprise
  // policy, and the bulk-pinning feature should not be available.
  if (profile &&
      !profile->GetPrefs()->GetBoolean(prefs::kDriveFsBulkPinningVisible)) {
    return false;
  }

  // For Googlers, the bulk-pinning feature is available on any kind of device.
  if (UserManager::IsInitialized()) {
    if (const User* const user = UserManager::Get()->GetActiveUser();
        user && gaia::IsGoogleInternalAccountEmail(
                    user->GetAccountId().GetUserEmail())) {
      return true;
    }
  }

  // For other users (non-Googlers), the bulk-pinning feature is available on
  // suitable devices, as controlled by the
  // "FeatureManagementDriveFsBulkPinning" Chrome feature.
  return base::FeatureList::IsEnabled(
      ash::features::kFeatureManagementDriveFsBulkPinning);
}

bool IsDriveFsBulkPinningAvailable() {
  return IsDriveFsBulkPinningAvailable(ProfileManager::GetActiveUserProfile());
}

bool IsOobeDrivePinningAvailable(const Profile* const profile) {
  const bool b = IsOobeDrivePinningScreenEnabled() &&
                 IsDriveFsBulkPinningAvailable(profile);
  VLOG(1) << "IsOobeDrivePinningAvailable() returned " << b;
  return b;
}

bool IsOobeDrivePinningAvailable() {
  return IsOobeDrivePinningAvailable(ProfileManager::GetActiveUserProfile());
}

// To ensure that the DrivePinningScreen is always available to the wizard,
// regardless of the current user profile, check this to add the
// DrivePinningScreen to the screen_manager when initializing the
// wizardController.
bool IsOobeDrivePinningScreenEnabled() {
  return base::FeatureList::IsEnabled(ash::features::kOobeDrivePinning) &&
         ash::features::IsOobeChoobeEnabled();
}

bool IsDriveFsMirrorSyncAvailable(const Profile* const profile) {
  return base::FeatureList::IsEnabled(ash::features::kDriveFsMirroring);
}

std::ostream& operator<<(std::ostream& out, const ConnectionStatus status) {
  switch (status) {
#define PRINT(s)               \
  case ConnectionStatus::k##s: \
    return out << #s;
    PRINT(NoService)
    PRINT(NoNetwork)
    PRINT(NotReady)
    PRINT(Metered)
    PRINT(Connected)
#undef PRINT
  }

  return out << "ConnectionStatus("
             << static_cast<std::underlying_type_t<ConnectionStatus>>(status)
             << ")";
}

// For testing.
static ConnectionStatus connection_status_for_testing;
static bool has_connection_status_for_testing = false;

void SetDriveConnectionStatusForTesting(const ConnectionStatus status) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  VLOG(1) << "SetDriveConnectionStatusForTesting: " << status;
  connection_status_for_testing = status;
  has_connection_status_for_testing = true;
}

ConnectionStatus GetDriveConnectionStatus(Profile* const profile) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  using enum ConnectionStatus;

  if (has_connection_status_for_testing) {
    VLOG(1) << "GetDriveConnectionStatus: for testing: "
            << connection_status_for_testing;
    return connection_status_for_testing;
  }

  if (!GetIntegrationServiceByProfile(profile)) {
    VLOG(1) << "GetDriveConnectionStatus: no Drive integration service";
    return kNoService;
  }

  if (!ash::NetworkHandler::IsInitialized()) {
    VLOG(1) << "GetDriveConnectionStatus: no network handler";
    return kNoNetwork;
  }

  ash::NetworkStateHandler* const handler =
      ash::NetworkHandler::Get()->network_state_handler();
  DCHECK(handler);

  const ash::NetworkState* const network = handler->DefaultNetwork();
  if (!network) {
    VLOG(1) << "GetDriveConnectionStatus: no network";
    return kNoNetwork;
  }

  if (!network->IsOnline()) {
    VLOG(1) << "GetDriveConnectionStatus: not ready: network is "
            << network->connection_state();
    return kNotReady;
  }

  using PortalState = ash::NetworkState::PortalState;
  if (const PortalState portal_state = network->GetPortalState();
      portal_state != PortalState::kOnline) {
    VLOG(1) << "GetDriveConnectionStatus: not ready: portal is "
            << portal_state;
    return kNotReady;
  }

  DCHECK(profile);
  if (profile->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular) &&
      handler->default_network_is_metered()) {
    VLOG(1) << "GetDriveConnectionStatus: metered";
    return kMetered;
  }

  VLOG(1) << "GetDriveConnectionStatus: connected";
  return kConnected;
}

bool IsPinnableGDocMimeType(const std::string& mime_type) {
  constexpr auto kPinnableGDocMimeTypes =
      base::MakeFixedFlatSet<std::string_view>({
          "application/vnd.google-apps.document",
          "application/vnd.google-apps.drawing",
          "application/vnd.google-apps.presentation",
          "application/vnd.google-apps.spreadsheet",
      });

  return kPinnableGDocMimeTypes.contains(mime_type);
}

int64_t ComputeDriveFsContentCacheSize(const base::FilePath& path) {
  int64_t blocks = 0;

  using base::FileEnumerator;
  FileEnumerator it(path, true, FileEnumerator::FILES);
  while (!it.Next().empty()) {
    const FileEnumerator::FileInfo& info = it.GetInfo();

    // Skip the `chunks.db*` files.
    if (base::StartsWith(info.GetName().value(), "chunks.db")) {
      continue;
    }

    blocks += info.stat().st_blocks;
  }

  const int64_t size = blocks << 9;
  VLOG(1) << "DriveFs cache: " << (size >> 20) << " M";
  return size;
}

}  // namespace drive::util