darkwing/server/dtos/entities/browser_profile_dto/
browser_profile.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
//! Data transfer objects for browser profile entities.
//!
//! This module contains the DTOs used to represent browser profiles and their
//! associated data when transferring between the database layer and the
//! application layer. It includes both minimal and full representations of
//! browser profiles.

use anyhow::Context;
use sqlx::prelude::FromRow;
use time::OffsetDateTime;

use crate::{
  database::browser_profile::{
    BrowserProfileWithFingerprint, Homepage, MainWebsite, Platform,
  },
  server::{dtos::settings_dto::SettingsList, error::AppResult},
};

use super::{
  super::{proxy_dto::ProxyFullData, settings_dto::SettingsFullData},
  Args, Canvas, CanvasJson, ClientRect, ClientRectJson, Cpu, CpuJson,
  Geolocation, GeolocationJson, Locale, LocaleJson, MediaDevices, Memory,
  MemoryJson, Ports, PortsJson, Screen, ScreenJson, Tabs, Timezone,
  TimezoneJson, Useragent, UseragentJson, WebGL2Maximum, WebGPUInfo, Webgl,
  WebglInfo, WebglInfoJson, WebglJson, Webrtc, WebrtcJson,
};

/// A minimal representation of a browser profile containing only essential
/// identifying information.
#[derive(Debug, Clone, FromRow)]
pub struct MiniBrowserProfile {
  /// Unique identifier for the browser profile
  pub id: u64,
  /// ID of the user who owns this profile
  pub user_id: i64,
  /// ID of the team this profile belongs to
  pub team_id: i64,
  /// Timestamp when the profile was created
  pub created_at: OffsetDateTime,
  /// Hash of the profile's data directory, if one exists
  pub datadir_hash: Option<String>,
}

impl From<BrowserProfileWithFingerprint> for MiniBrowserProfile {
  /// Creates a minimal profile from a full profile with fingerprint data
  fn from(browser_profile: BrowserProfileWithFingerprint) -> Self {
    Self {
      id: browser_profile.id,
      user_id: browser_profile.user_id,
      team_id: browser_profile.team_id,
      created_at: browser_profile.created_at,
      datadir_hash: browser_profile.datadir_hash,
    }
  }
}

impl From<BrowserProfileFullData> for MiniBrowserProfile {
  /// Creates a minimal profile from a full profile
  fn from(browser_profile: BrowserProfileFullData) -> Self {
    Self {
      id: browser_profile.id,
      user_id: browser_profile.user_id,
      team_id: browser_profile.team_id,
      created_at: browser_profile.created_at,
      datadir_hash: None,
    }
  }
}

/// Complete representation of a browser profile including all fingerprinting
/// and configuration data.
#[derive(Debug, Clone)]
pub struct BrowserProfileFullData {
  /// Unique identifier for the browser profile
  pub id: u64,
  /// ID of the user who owns this profile
  pub user_id: i64,
  /// ID of the team this profile belongs to
  pub team_id: i64,
  /// Display name of the browser profile
  pub name: String,
  /// Primary website associated with this profile
  pub main_website: MainWebsite,
  /// Operating system platform configuration
  pub platform: Platform,
  /// User agent configuration
  pub useragent: Useragent,
  /// WebRTC configuration
  pub webrtc: Webrtc,
  /// Canvas fingerprinting configuration
  pub canvas: Canvas,
  /// WebGL configuration
  pub webgl: Webgl,
  /// WebGL capabilities and info configuration
  pub webgl_info: WebglInfo,
  /// Client rectangle/viewport configuration
  pub client_rect: ClientRect,
  /// Timezone configuration
  pub timezone: Timezone,
  /// Locale and language configuration
  pub locale: Locale,
  /// Geolocation configuration
  pub geolocation: Geolocation,
  /// Whether Do Not Track is enabled
  pub do_not_track: bool,
  /// Command line arguments configuration
  pub args: Args,
  /// CPU configuration
  pub cpu: Cpu,
  /// Memory configuration
  pub memory: Memory,
  /// Screen resolution configuration
  pub screen: Screen,
  /// Network ports configuration
  pub ports: Ports,
  /// Browser tabs configuration
  pub tabs: Tabs,
  /// CPU architecture string
  pub cpu_architecture: String,
  /// Operating system version string
  pub os_version: String,
  /// Network connection downlink speed in Mbps
  pub connection_downlink: f64,
  /// Network connection type (4g, 3g etc)
  pub connection_effective_type: String,
  /// Network round trip time in milliseconds
  pub connection_rtt: u32,
  /// Whether data saver is enabled
  pub connection_save_data: bool,
  /// Browser vendor sub string
  pub vendor_sub: String,
  /// Browser product sub string
  pub product_sub: String,
  /// Browser vendor string
  pub vendor: String,
  /// Browser product string
  pub product: String,
  /// Browser code name
  pub app_code_name: String,
  /// Media devices configuration
  pub media_devices: MediaDevices,
  /// Hash of the profile's data directory
  pub datadir_hash: Option<String>,
  /// Platform version string
  pub platform_version: String,
  /// WebGL2 capability limits
  pub webgl2_maximum: Option<WebGL2Maximum>,
  /// Optional login credential
  pub login: Option<String>,
  /// Optional password credential
  pub password: Option<String>,
  /// Whether the profile name should be hidden
  pub is_hidden_profile_name: bool,
  /// WebGPU configuration
  pub webgpu: WebGPUInfo,
  /// Browser settings configuration
  pub settings: SettingsList,
  /// Optional proxy configuration
  pub proxy: Option<ProxyFullData>,
  /// Timestamp when the profile was created
  pub created_at: OffsetDateTime,
  /// List of configured homepages
  pub homepages: Vec<Homepage>,
}

impl BrowserProfileFullData {
  /// Creates a new full browser profile from its components.
  ///
  /// # Arguments
  /// * `browser_profile_fingerprint` - The base profile data with fingerprint
  ///   information
  /// * `settings` - List of browser settings
  /// * `proxy` - Optional proxy configuration
  /// * `homepages` - List of homepage configurations
  ///
  /// # Returns
  /// * `AppResult<Self>` - The constructed profile or an error if parsing fails
  ///
  /// # Errors
  /// Returns an error if parsing any of the JSON fields fails
  pub async fn new(
    browser_profile_fingerprint: BrowserProfileWithFingerprint,
    settings: Vec<SettingsFullData>,
    proxy: Option<ProxyFullData>,
    homepages: Vec<Homepage>,
  ) -> AppResult<Self> {
    let useragent: UseragentJson =
      serde_json::from_str(&browser_profile_fingerprint.useragent)
        .context("Failed to parse useragent")?;
    let useragent = Useragent::try_from(useragent)?;

    let screen: ScreenJson =
      serde_json::from_str(&browser_profile_fingerprint.screen)
        .context("Failed to parse screen")?;
    let screen = Screen::try_from(screen)?;

    let client_rect: ClientRectJson =
      serde_json::from_str(&browser_profile_fingerprint.client_rect)
        .context("Failed to parse client rect")?;
    let client_rect = ClientRect::try_from(client_rect)?;

    let webrtc: WebrtcJson =
      serde_json::from_str(&browser_profile_fingerprint.webrtc)
        .context("Failed to parse webrtc")?;
    let webrtc = Webrtc::try_from(webrtc)?;

    let canvas: CanvasJson =
      serde_json::from_str(&browser_profile_fingerprint.canvas)
        .context("Failed to parse canvas")?;
    let canvas = Canvas::try_from(canvas)?;

    let webgl: WebglJson =
      serde_json::from_str(&browser_profile_fingerprint.webgl)
        .context("Failed to parse webgl")?;
    let webgl = Webgl::try_from(webgl)?;

    let webgl_info: WebglInfoJson =
      serde_json::from_str(&browser_profile_fingerprint.webgl_info)
        .context("Failed to parse webgl_info")?;

    // browser_profiles.webgl2Maximum column is NOT used, there is no records
    // with it being not null. webgl2maximum is always filled into
    // browser_profiles.webgl_info.webgl2_maximum so we're just using
    // browser_profiles.webgl_info.webgl2_maximum
    let webgl2_maximum = match webgl_info.webgl2_maximum {
      Some(ref webgl2_maximum) => serde_json::from_str(webgl2_maximum)
        .context("Failed to parse webgl2_maximum")?,
      None => None,
    };

    let webgl_info = WebglInfo::try_from(webgl_info)?;

    let ports: PortsJson =
      serde_json::from_str(&browser_profile_fingerprint.ports)
        .context("Failed to parse ports")?;
    let ports = Ports::try_from(ports)?;

    let media_devices =
      MediaDevices::try_from(browser_profile_fingerprint.media_devices)?;

    let do_not_track = browser_profile_fingerprint.do_not_track == 1;
    let connection_save_data =
      browser_profile_fingerprint.connection_save_data == 1;

    let cpu: CpuJson = serde_json::from_str(&browser_profile_fingerprint.cpu)
      .context("Failed to parse cpu")?;
    let cpu = Cpu::try_from(cpu)?;

    let memory: MemoryJson =
      serde_json::from_str(&browser_profile_fingerprint.memory)
        .context("Failed to parse memory")?;
    let memory = Memory::try_from(memory)?;

    let args = Args::from_json_str(&browser_profile_fingerprint.args)
      .context("Failed to parse args")?;

    let geolocation: GeolocationJson =
      serde_json::from_str(&browser_profile_fingerprint.geolocation)
        .context("Failed to parse geolocation")?;
    let geolocation = Geolocation::try_from(geolocation)?;

    let timezone: TimezoneJson =
      serde_json::from_str(&browser_profile_fingerprint.timezone)
        .context("Failed to parse timezone")?;
    let timezone = Timezone::try_from(timezone)?;

    let locale: LocaleJson =
      serde_json::from_str(&browser_profile_fingerprint.locale)
        .context("Failed to parse locale")?;
    let locale = Locale::try_from(locale)?;

    let tabs_from_browser_profile_table: Result<Tabs, serde_json::Error> =
      serde_json::from_str(&browser_profile_fingerprint.tabs);

    let tabs_from_special_table: Result<Tabs, serde_json::Error> =
      serde_json::from_str(
        &browser_profile_fingerprint
          .browser_profile_tabs
          .unwrap_or("".to_string()),
      );

    // TODO: homepages in last unwrap_or
    let tabs = tabs_from_special_table
      .unwrap_or(tabs_from_browser_profile_table.unwrap_or(Tabs(vec![])));

    let webgpu = WebGPUInfo::try_from(browser_profile_fingerprint.webgpu)
      .context("Failed to parse webgpu (new)")?;

    let main_website =
      MainWebsite::from(browser_profile_fingerprint.main_website);

    let settings = SettingsList::new(settings);

    Ok(Self {
      id: browser_profile_fingerprint.id,
      user_id: browser_profile_fingerprint.user_id,
      team_id: browser_profile_fingerprint.team_id,
      name: browser_profile_fingerprint.name,
      main_website,
      platform: Platform::from(browser_profile_fingerprint.platform),
      useragent,
      webrtc,
      canvas,
      webgl,
      webgl_info,
      client_rect,
      timezone,
      locale,
      geolocation,
      do_not_track,
      args,
      cpu,
      memory,
      screen,
      ports,
      tabs,
      cpu_architecture: browser_profile_fingerprint.cpu_architecture,
      os_version: browser_profile_fingerprint.os_version,
      connection_downlink: browser_profile_fingerprint.connection_downlink,
      connection_effective_type: browser_profile_fingerprint
        .connection_effective_type,
      connection_rtt: browser_profile_fingerprint.connection_rtt,
      connection_save_data,
      vendor_sub: browser_profile_fingerprint.vendor_sub,
      product_sub: browser_profile_fingerprint.product_sub,
      vendor: browser_profile_fingerprint.vendor,
      product: browser_profile_fingerprint.product,
      app_code_name: browser_profile_fingerprint.app_code_name,
      media_devices,
      datadir_hash: browser_profile_fingerprint.datadir_hash,
      platform_version: browser_profile_fingerprint.platform_version,
      webgl2_maximum,
      is_hidden_profile_name: browser_profile_fingerprint
        .is_hidden_profile_name
        .unwrap_or(false),
      login: browser_profile_fingerprint.login,
      password: browser_profile_fingerprint.password,
      webgpu,
      settings,
      proxy,
      created_at: browser_profile_fingerprint.created_at,
      homepages,
    })
  }
}