Skip to content

Commit 7d17f34

Browse files
authored
perf: avoid unnecessary parsing of user-agents (#4449)
2 parents 7e715fb + 0cfea60 commit 7d17f34

File tree

33 files changed

+112
-111
lines changed

33 files changed

+112
-111
lines changed

crates/data-model/src/compat/session.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use serde::Serialize;
1111
use ulid::Ulid;
1212

1313
use super::Device;
14-
use crate::{InvalidTransitionError, UserAgent};
14+
use crate::InvalidTransitionError;
1515

1616
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
1717
pub enum CompatSessionState {
@@ -76,7 +76,7 @@ pub struct CompatSession {
7676
pub user_session_id: Option<Ulid>,
7777
pub created_at: DateTime<Utc>,
7878
pub is_synapse_admin: bool,
79-
pub user_agent: Option<UserAgent>,
79+
pub user_agent: Option<String>,
8080
pub last_active_at: Option<DateTime<Utc>>,
8181
pub last_active_ip: Option<IpAddr>,
8282
}

crates/data-model/src/oauth2/device_code_grant.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use oauth2_types::scope::Scope;
1111
use serde::Serialize;
1212
use ulid::Ulid;
1313

14-
use crate::{BrowserSession, InvalidTransitionError, Session, UserAgent};
14+
use crate::{BrowserSession, InvalidTransitionError, Session};
1515

1616
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1717
#[serde(rename_all = "snake_case", tag = "state")]
@@ -192,7 +192,7 @@ pub struct DeviceCodeGrant {
192192
pub ip_address: Option<IpAddr>,
193193

194194
/// The user agent used to request this device code grant.
195-
pub user_agent: Option<UserAgent>,
195+
pub user_agent: Option<String>,
196196
}
197197

198198
impl std::ops::Deref for DeviceCodeGrant {

crates/data-model/src/oauth2/session.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use oauth2_types::scope::Scope;
1111
use serde::Serialize;
1212
use ulid::Ulid;
1313

14-
use crate::{InvalidTransitionError, UserAgent};
14+
use crate::InvalidTransitionError;
1515

1616
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
1717
pub enum SessionState {
@@ -80,7 +80,7 @@ pub struct Session {
8080
pub user_session_id: Option<Ulid>,
8181
pub client_id: Ulid,
8282
pub scope: Scope,
83-
pub user_agent: Option<UserAgent>,
83+
pub user_agent: Option<String>,
8484
pub last_active_at: Option<DateTime<Utc>>,
8585
pub last_active_ip: Option<IpAddr>,
8686
}

crates/data-model/src/user_agent.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44
// SPDX-License-Identifier: AGPL-3.0-only
55
// Please see LICENSE in the repository root for full details.
66

7+
use std::sync::LazyLock;
8+
79
use serde::Serialize;
810
use woothee::{parser::Parser, woothee::VALUE_UNKNOWN};
911

12+
static CUSTOM_USER_AGENT_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
13+
regex::Regex::new(r"^(?P<name>[^/]+)/(?P<version>[^ ]+) \((?P<segments>.+)\)$").unwrap()
14+
});
15+
16+
static ELECTRON_USER_AGENT_REGEX: LazyLock<regex::Regex> =
17+
LazyLock::new(|| regex::Regex::new(r"(?m)\w+/[\w.]+").unwrap());
18+
1019
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
1120
#[serde(rename_all = "snake_case")]
1221
pub enum DeviceType {
@@ -37,10 +46,7 @@ impl std::ops::Deref for UserAgent {
3746

3847
impl UserAgent {
3948
fn parse_custom(user_agent: &str) -> Option<(&str, &str, &str, &str, Option<&str>)> {
40-
let regex = regex::Regex::new(r"^(?P<name>[^/]+)/(?P<version>[^ ]+) \((?P<segments>.+)\)$")
41-
.unwrap();
42-
43-
let captures = regex.captures(user_agent)?;
49+
let captures = CUSTOM_USER_AGENT_REGEX.captures(user_agent)?;
4450
let name = captures.name("name")?.as_str();
4551
let version = captures.name("version")?.as_str();
4652
let segments: Vec<&str> = captures
@@ -73,9 +79,8 @@ impl UserAgent {
7379
}
7480

7581
fn parse_electron(user_agent: &str) -> Option<(&str, &str)> {
76-
let regex = regex::Regex::new(r"(?m)\w+/[\w.]+").unwrap();
7782
let omit_keys = ["Mozilla", "AppleWebKit", "Chrome", "Electron", "Safari"];
78-
return regex
83+
return ELECTRON_USER_AGENT_REGEX
7984
.find_iter(user_agent)
8085
.map(|caps| caps.as_str().split_once('/').unwrap())
8186
.find(|pair| !omit_keys.contains(&pair.0));

crates/data-model/src/users.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ use serde::Serialize;
1212
use ulid::Ulid;
1313
use url::Url;
1414

15-
use crate::UserAgent;
16-
1715
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1816
pub struct User {
1917
pub id: Ulid,
@@ -81,7 +79,7 @@ pub enum AuthenticationMethod {
8179
pub struct UserRecoverySession {
8280
pub id: Ulid,
8381
pub email: String,
84-
pub user_agent: UserAgent,
82+
pub user_agent: String,
8583
pub ip_address: Option<IpAddr>,
8684
pub locale: String,
8785
pub created_at: DateTime<Utc>,
@@ -137,7 +135,7 @@ pub struct BrowserSession {
137135
pub user: User,
138136
pub created_at: DateTime<Utc>,
139137
pub finished_at: Option<DateTime<Utc>>,
140-
pub user_agent: Option<UserAgent>,
138+
pub user_agent: Option<String>,
141139
pub last_active_at: Option<DateTime<Utc>>,
142140
pub last_active_ip: Option<IpAddr>,
143141
}
@@ -159,9 +157,9 @@ impl BrowserSession {
159157
user,
160158
created_at: now,
161159
finished_at: None,
162-
user_agent: Some(UserAgent::parse(
160+
user_agent: Some(
163161
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36".to_owned()
164-
)),
162+
),
165163
last_active_at: Some(now),
166164
last_active_ip: None,
167165
})
@@ -213,7 +211,7 @@ pub struct UserRegistration {
213211
pub password: Option<UserRegistrationPassword>,
214212
pub post_auth_action: Option<serde_json::Value>,
215213
pub ip_address: Option<IpAddr>,
216-
pub user_agent: Option<UserAgent>,
214+
pub user_agent: Option<String>,
217215
pub created_at: DateTime<Utc>,
218216
pub completed_at: Option<DateTime<Utc>>,
219217
}

crates/handlers/src/admin/model.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ impl
206206
user_session_id: session.user_session_id,
207207
redirect_uri: sso_login.map(|sso| sso.redirect_uri),
208208
created_at: session.created_at,
209-
user_agent: session.user_agent.map(|ua| ua.raw),
209+
user_agent: session.user_agent,
210210
last_active_at: session.last_active_at,
211211
last_active_ip: session.last_active_ip,
212212
finished_at,
@@ -313,7 +313,7 @@ impl From<mas_data_model::Session> for OAuth2Session {
313313
user_session_id: session.user_session_id,
314314
client_id: session.client_id,
315315
scope: session.scope.to_string(),
316-
user_agent: session.user_agent.map(|ua| ua.raw),
316+
user_agent: session.user_agent,
317317
last_active_at: session.last_active_at,
318318
last_active_ip: session.last_active_ip,
319319
}
@@ -406,7 +406,7 @@ impl From<mas_data_model::BrowserSession> for UserSession {
406406
created_at: value.created_at,
407407
finished_at: value.finished_at,
408408
user_id: value.user.id,
409-
user_agent: value.user_agent.map(|ua| ua.raw),
409+
user_agent: value.user_agent,
410410
last_active_at: value.last_active_at,
411411
last_active_ip: value.last_active_ip,
412412
}

crates/handlers/src/compat/login.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ use axum_extra::typed_header::TypedHeader;
1111
use chrono::Duration;
1212
use hyper::StatusCode;
1313
use mas_axum_utils::record_error;
14-
use mas_data_model::{
15-
CompatSession, CompatSsoLoginState, Device, SiteConfig, TokenType, User, UserAgent,
16-
};
14+
use mas_data_model::{CompatSession, CompatSsoLoginState, Device, SiteConfig, TokenType, User};
1715
use mas_matrix::HomeserverConnection;
1816
use mas_storage::{
1917
BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
@@ -276,7 +274,7 @@ pub(crate) async fn post(
276274
user_agent: Option<TypedHeader<headers::UserAgent>>,
277275
MatrixJsonBody(input): MatrixJsonBody<RequestBody>,
278276
) -> Result<impl IntoResponse, RouteError> {
279-
let user_agent = user_agent.map(|ua| UserAgent::parse(ua.as_str().to_owned()));
277+
let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
280278
let login_type = input.credentials.login_type();
281279
let (mut session, user) = match (password_manager.is_enabled(), input.credentials) {
282280
(

crates/handlers/src/graphql/model/browser_sessions.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ impl BrowserSession {
8181

8282
/// The user-agent with which the session was created.
8383
pub async fn user_agent(&self) -> Option<UserAgent> {
84-
self.0.user_agent.clone().map(UserAgent::from)
84+
self.0
85+
.user_agent
86+
.clone()
87+
.map(mas_data_model::UserAgent::parse)
88+
.map(UserAgent::from)
8589
}
8690

8791
/// The last IP address used by the session.

crates/handlers/src/graphql/model/compat_sessions.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ impl CompatSession {
9898

9999
/// The user-agent with which the session was created.
100100
pub async fn user_agent(&self) -> Option<UserAgent> {
101-
self.session.user_agent.clone().map(UserAgent::from)
101+
self.session
102+
.user_agent
103+
.clone()
104+
.map(mas_data_model::UserAgent::parse)
105+
.map(UserAgent::from)
102106
}
103107

104108
/// The associated SSO login, if any.

crates/handlers/src/graphql/model/oauth.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ impl OAuth2Session {
6161

6262
/// The user-agent with which the session was created.
6363
pub async fn user_agent(&self) -> Option<UserAgent> {
64-
self.0.user_agent.clone().map(UserAgent::from)
64+
self.0
65+
.user_agent
66+
.clone()
67+
.map(mas_data_model::UserAgent::parse)
68+
.map(UserAgent::from)
6569
}
6670

6771
/// The state of the session.

0 commit comments

Comments
 (0)