Expand description
§User Agent Parser
A parser to get the product, OS, device, cpu, and engine information from a user agent, inspired by https://github.com/faisalman/ua-parser-js and https://github.com/ua-parser/uap-core
§Usage
You can make a regexes.yaml file or copy one from https://github.com/ua-parser/uap-core
This is a simple example of regexes.yaml.
user_agent_parsers:
- regex: '(ESPN)[%20| ]+Radio/(\d+)\.(\d+)\.(\d+) CFNetwork'
- regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)\.(\d+(?:pre|))'
family_replacement: 'Firefox ($1)'
- regex: '(Android) Eclair'
v1_replacement: '2'
v2_replacement: '1'
os_parsers:
- regex: 'Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE)'
os_replacement: 'Windows'
os_v1_replacement: '$1'
device_parsers:
- regex: '\bSmartWatch *\( *([^;]+) *; *([^;]+) *;'
device_replacement: '$1 $2'
brand_replacement: '$1'
model_replacement: '$2'The CPU and the layout engine are not a part of the uap-core format, so this crate ships its own patterns for them. The optional cpu_parsers and engine_parsers sections replace those built-in patterns.
cpu_parsers:
- regex: 'sun4\w[;)]'
architecture_replacement: 'sparc'
- regex: '\b(riscv\d*)\b'
engine_parsers:
- regex: '(myengine)/(\w+)(?:\.(\w+))?(?:\.(\w+))?'
engine_replacement: 'MyEngine'In cpu_parsers, the first capture group is the architecture. In engine_parsers, the first one is the name, and the second, third and fourth ones are the major, minor and patch versions. A *_replacement overrides the group it stands for, just like in the sections above. Leaving a section out keeps every built-in pattern, and writing one down replaces all of them.
Then, use the from_path (or from_str if your YAML data is in-memory) associated function to create a UserAgentParser instance.
use user_agent_parser::UserAgentParser;
let ua_parser = UserAgentParser::from_path("/path/to/regexes.yaml").unwrap();Use the parse_* methods and input a user-agent string to get information.
use user_agent_parser::UserAgentParser;
let ua_parser = UserAgentParser::from_path("/path/to/regexes.yaml").unwrap();
let user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]";
let product = ua_parser.parse_product(user_agent);
println!("{:#?}", product);
// Product {
// name: Some(
// "Facebook",
// ),
// major: Some(
// "8",
// ),
// minor: Some(
// "0",
// ),
// patch: Some(
// "0",
// ),
// patch_minor: None,
// }
let os = ua_parser.parse_os(user_agent);
println!("{:#?}", os);
// OS {
// name: Some(
// "iOS",
// ),
// major: None,
// minor: None,
// patch: None,
// patch_minor: None,
// }
let device = ua_parser.parse_device(user_agent);
println!("{:#?}", device);
// Device {
// name: Some(
// "iPhone",
// ),
// brand: Some(
// "Apple",
// ),
// model: Some(
// "iPhone4,1",
// ),
// }
let cpu = ua_parser.parse_cpu(user_agent);
println!("{:#?}", cpu);
// CPU {
// architecture: Some(
// "amd64",
// ),
// }
let engine = ua_parser.parse_engine(user_agent);
println!("{:#?}", engine);
// Engine {
// name: Some(
// "Gecko",
// ),
// major: Some(
// "10",
// ),
// minor: Some(
// "0",
// ),
// patch: None,
// }The Product, OS and Engine models can also join their version parts into one string with the version method. It starts at the major version and stops at the first part which is missing.
use user_agent_parser::UserAgentParser;
let ua_parser = UserAgentParser::from_path("/path/to/regexes.yaml").unwrap();
let product = ua_parser.parse_product("Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36");
assert_eq!(Some("79.0.3945.79"), product.version().as_deref());The lifetime of result instances of the parse_* methods depends on the user-agent string and the UserAgentParser instance. To make it independent, call the into_owned method.
use user_agent_parser::UserAgentParser;
let ua_parser = UserAgentParser::from_path("/path/to/regexes.yaml").unwrap();
let product = ua_parser.parse_product("Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12").into_owned();§Rocket Support
This crate supports the Rocket framework. All you have to do is enabling the rocket feature for this crate.
[dependencies.user-agent-parser]
version = "*"
features = ["rocket"]Let Rocket manage a UserAgentParser instance, and the Product, OS, Device, CPU, Engine models of this crate (plus the UserAgent model) can be used as Request Guards.
The Product, OS, Device, CPU and Engine guards panic if no UserAgentParser is managed, because that is a mistake in the setup rather than something a single request can recover from. The UserAgent guard needs no parser at all. A request without a User-Agent header is fine and yields the default model.
#[macro_use]
extern crate rocket;
use user_agent_parser::{UserAgentParser, UserAgent, Product, OS, Device, CPU, Engine};
#[get("/")]
fn index(user_agent: UserAgent, product: Product, os: OS, device: Device, cpu: CPU, engine: Engine) -> String {
format!("{user_agent:#?}\n{product:#?}\n{os:#?}\n{device:#?}\n{cpu:#?}\n{engine:#?}",
user_agent = user_agent,
product = product,
os = os,
device = device,
cpu = cpu,
engine = engine,
)
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(UserAgentParser::from_path("/path/to/regexes.yaml").unwrap())
.mount("/", routes![index])
}§Axum Support
This crate also supports the Axum framework. All you have to do is enabling the axum feature for this crate.
[dependencies.user-agent-parser]
version = "*"
features = ["axum"]Share a UserAgentParser instance through an Extension<Arc<UserAgentParser>> layer, and the owned models Product<'static>, OS<'static>, Device<'static>, CPU<'static>, Engine<'static> (plus the UserAgent<'static> model) can be used as extractors.
The Product, OS, Device, CPU and Engine extractors panic if that layer is missing, because that is a mistake in the setup rather than something a single request can recover from. The UserAgent extractor needs no parser at all. A request without a User-Agent header is fine and yields the default model.
use std::sync::Arc;
use axum::{routing::get, Extension, Router};
use user_agent_parser::{UserAgentParser, UserAgent, Product, OS, Device, CPU, Engine};
async fn index(
user_agent: UserAgent<'static>,
product: Product<'static>,
os: OS<'static>,
device: Device<'static>,
cpu: CPU<'static>,
engine: Engine<'static>,
) -> String {
format!("{user_agent:#?}\n{product:#?}\n{os:#?}\n{device:#?}\n{cpu:#?}\n{engine:#?}")
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(index))
.layer(Extension(Arc::new(UserAgentParser::from_path("/path/to/regexes.yaml").unwrap())));
let listener = tokio::net::TcpListener::bind("127.0.0.1:8000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}§Testing
# git clone --recurse-submodules https://github.com/magiclen/user-agent-parser.git
git clone https://github.com/magiclen/user-agent-parser.git
cd user-agent-parser
git submodule init
git submodule update --recursive
cargo testStructs§
- CPU
- The CPU architecture a user agent runs on.
- Device
- The physical device a user agent runs on.
- Engine
- The layout engine of the product a user agent belongs to.
- OS
- The operating system a user agent runs on.
- Product
- The product (usually the browser) a user agent belongs to.
- User
Agent axumorrocket - The raw
User-Agentheader of a request. - User
Agent Parser - A set of regular expressions which can extract the product, OS, device, CPU and engine information out of a user-agent string.
Enums§
- User
Agent Parser Error - Possible errors of
UserAgentParser.