User Agent Parser
====================
[](https://github.com/magiclen/user-agent-parser/actions/workflows/ci.yml)
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**.
```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:
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.
```yaml
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.
```rust
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.
```rust
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.
```rust
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.
```rust
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.
```toml
[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.
```rust
#[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.
```toml
[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.
```rust
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
```bash
# 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 test
```
## Crates.io
https://crates.io/crates/user-agent-parser
## Documentation
https://docs.rs/user-agent-parser
## License
[MIT](LICENSE)