Merge pull request #5 from redlib-org/improve_spoofing

Improve spoofing
This commit is contained in:
Matthew Esposito 2023-12-28 15:44:52 -05:00 committed by GitHub
commit d4c4d61ce8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 426 additions and 77 deletions

View File

@ -7,6 +7,7 @@ use std::os::unix::process::ExitStatusExt;
use std::os::windows::process::ExitStatusExt;
fn main() {
println!("cargo:rerun-if-changed=src/");
let output = String::from_utf8(
Command::new("git")
.args(["rev-parse", "HEAD"])

104
scripts/update_oauth_resources.sh Executable file
View File

@ -0,0 +1,104 @@
#!/bin/bash
# Requirements
# - curl
# - rg
# - jq
# Fetch iOS app versions
ios_version_list=$(curl -s "https://ipaarchive.com/app/usa/1064216828" | rg "(20\d{2}\.\d+.\d+) / (\d+)" --only-matching -r "Version \$1/Build \$2" | sort | uniq)
# Count the number of lines in the version list
ios_app_count=$(echo "$ios_version_list" | wc -l)
echo -e "Fetching \e[34m$ios_app_count iOS app versions...\e[0m"
# Specify the filename as a variable
filename="src/oauth_resources.rs"
# Add comment that it is user generated
echo "// This file was generated by scripts/update_oauth_resources.sh" >> "$filename"
echo "// Rerun scripts/update_oauth_resources.sh to update this file" >> "$filename"
echo "// Please do not edit manually" >> "$filename"
echo "// Filled in with real app versions" >> "$filename"
# Open the array in the source file
echo "pub static IOS_APP_VERSION_LIST: &[&str; $ios_app_count] = &[" >> "$filename"
# Append the version list to the source file
echo "$ios_version_list" | while IFS= read -r line; do
echo " \"$line\"," >> "$filename"
echo -e "Fetched \e[34m$line\e[0m."
done
# Close the array in the source file
echo "];" >> "$filename"
# Fetch Android app versions
page_1=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions/" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
# Append with pages
page_2=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=2" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
page_3=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=3" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
page_4=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=4" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
page_5=$(curl -s "https://apkcombo.com/reddit/com.reddit.frontpage/old-versions?page=5" | rg "<a class=\"ver-item\" href=\"(/reddit/com\.reddit\.frontpage/download/phone-20\d{2}\.\d+\.\d+-apk)\" rel=\"nofollow\">" -r "https://apkcombo.com\$1" | sort | uniq)
# Concatenate all pages
versions="${page_1}"
versions+=$'\n'
versions+="${page_2}"
versions+=$'\n'
versions+="${page_3}"
versions+=$'\n'
versions+="${page_4}"
versions+=$'\n'
versions+="${page_5}"
# Count the number of lines in the version list
android_count=$(echo "$versions" | wc -l)
echo -e "Fetching \e[32m$android_count Android app versions...\e[0m"
# Append to the source file
echo "pub static ANDROID_APP_VERSION_LIST: &[&str; $android_count] = &[" >> "$filename"
# For each in versions, curl the page and extract the build number
echo "$versions" | while IFS= read -r line; do
fetch_page=$(curl -s "$line")
build=$(echo "$fetch_page" | rg "<span class=\"vercode\">\((\d+)\)</span>" --only-matching -r "\$1" | head -n1)
version=$(echo "$fetch_page" | rg "<span class=\"vername\">Reddit (20\d{2}\.\d+\.\d+)</span>" --only-matching -r "\$1" | head -n1)
echo " \"Version $version/Build $build\"," >> "$filename"
echo -e "Fetched \e[32mVersion $version/Build $build\e[0m"
done
# Close the array in the source file
echo "];" >> "$filename"
# Retrieve iOS versions
table=$(curl -s "https://en.wikipedia.org/w/api.php?action=parse&page=IOS_17&prop=wikitext&section=31&format=json" | jq ".parse.wikitext.\"*\"" | rg "(17\.[\d\.]*)\\\n\|(\w*)\\\n\|" --only-matching -r "Version \$1 (Build \$2)")
# Count the number of lines in the version list
ios_count=$(echo "$table" | wc -l)
echo -e "Fetching \e[34m$ios_count iOS versions...\e[0m"
# Append to the source file
echo "pub static IOS_OS_VERSION_LIST: &[&str; $ios_count] = &[" >> "$filename"
# For each in versions, curl the page and extract the build number
echo "$table" | while IFS= read -r line; do
echo " \"$line\"," >> "$filename"
echo -e "Fetched $line\e[0m"
done
# Close the array in the source file
echo "];" >> "$filename"
echo -e "\e[34mRetrieved $ios_app_count iOS app versions.\e[0m"
echo -e "\e[32mRetrieved $android_count Android app versions.\e[0m"
echo -e "\e[34mRetrieved $ios_count iOS versions.\e[0m"
echo -e "\e[34mTotal: $((ios_app_count + android_count + ios_count))\e[0m"
echo -e "\e[32mSuccess!\e[0m"

View File

@ -20,12 +20,12 @@ use crate::server::RequestExt;
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
pub(crate) static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
pub static CLIENT: Lazy<Client<HttpsConnector<HttpConnector>>> = Lazy::new(|| {
let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build();
client::Client::builder().build(https)
});
pub(crate) static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| {
pub static OAUTH_CLIENT: Lazy<RwLock<Oauth>> = Lazy::new(|| {
let client = block_on(Oauth::new());
tokio::spawn(token_daemon());
RwLock::new(client)
@ -332,7 +332,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_localization_popular() {
let val = json("/r/popular/hot.json?&raw_json=1&geo_filter=GLOBAL".to_string(), false).await.unwrap();
assert_eq!("GLOBAL", val["data"]["geo_filter"].as_str().unwrap());

View File

@ -7,11 +7,11 @@ use std::{env::var, fs::read_to_string};
//
// This is the local static that is initialized at runtime (technically at
// first request) and contains the instance settings.
pub(crate) static CONFIG: Lazy<Config> = Lazy::new(Config::load);
pub static CONFIG: Lazy<Config> = Lazy::new(Config::load);
// This serves as the frontend for the Pushshift API - on removed comments, this URL will
// be the base of a link, to display removed content (on another site).
pub(crate) const DEFAULT_PUSHSHIFT_FRONTEND: &str = "www.unddit.com";
pub const DEFAULT_PUSHSHIFT_FRONTEND: &str = "www.unddit.com";
/// Stores the configuration parsed from the environment variables and the
/// config file. `Config::Default()` contains None for each setting.
@ -104,7 +104,7 @@ impl Config {
pub fn load() -> Self {
let load_config = |name: &str| {
let new_file = read_to_string(name);
new_file.ok().and_then(|new_file| toml::from_str::<Config>(&new_file).ok())
new_file.ok().and_then(|new_file| toml::from_str::<Self>(&new_file).ok())
};
let config = load_config("redlib.toml").or(load_config("libreddit.toml")).unwrap_or_default();
@ -168,7 +168,7 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
}
/// Retrieves setting from environment variable or config file.
pub(crate) fn get_setting(name: &str) -> Option<String> {
pub fn get_setting(name: &str) -> Option<String> {
get_setting_from_config(name, &CONFIG)
}

View File

@ -15,7 +15,7 @@ use time::OffsetDateTime;
// This is the local static that is intialized at runtime (technically at
// the first request to the info endpoint) and contains the data
// retrieved from the info endpoint.
pub(crate) static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
pub static INSTANCE_INFO: Lazy<InstanceInfo> = Lazy::new(InstanceInfo::new);
/// Handles instance info endpoint
pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String> {
@ -84,7 +84,7 @@ fn info_html(req: Request<Body>) -> Result<Response<Body>, Error> {
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(Body::from(message))
}
#[derive(Serialize, Deserialize, Default)]
pub(crate) struct InstanceInfo {
pub struct InstanceInfo {
package_name: String,
crate_version: String,
git_commit: String,

View File

@ -7,6 +7,7 @@ mod config;
mod duplicates;
mod instance_info;
mod oauth;
mod oauth_resources;
mod post;
mod search;
mod settings;

View File

@ -1,6 +1,9 @@
use std::{collections::HashMap, time::Duration};
use crate::client::{CLIENT, OAUTH_CLIENT};
use crate::{
client::{CLIENT, OAUTH_CLIENT},
oauth_resources::{ANDROID_APP_VERSION_LIST, IOS_APP_VERSION_LIST, IOS_OS_VERSION_LIST},
};
use base64::{engine::general_purpose, Engine as _};
use hyper::{client, Body, Method, Request};
use log::info;
@ -12,26 +15,10 @@ static REDDIT_IOS_OAUTH_CLIENT_ID: &str = "LNDo9k1o8UAEUw";
static AUTH_ENDPOINT: &str = "https://accounts.reddit.com";
// Various Android user agents - build numbers from valid APK variants
pub(crate) static ANDROID_USER_AGENT: [&str; 3] = [
"Reddit/Version 2023.21.0/Build 956283/Android 13",
"Reddit/Version 2023.21.0/Build 968223/Android 10",
"Reddit/Version 2023.21.0/Build 946732/Android 12",
];
// Various iOS user agents - iOS versions.
pub(crate) static IOS_USER_AGENT: [&str; 3] = [
"Reddit/Version 2023.22.0/Build 613580/iOS Version 17.0 (Build 21A5248V)",
"Reddit/Version 2023.22.0/Build 613580/iOS Version 16.0 (Build 20A5328h)",
"Reddit/Version 2023.22.0/Build 613580/iOS Version 16.5",
];
// Various iOS device codes. iPhone 11 displays as `iPhone12,1`
// I just changed the number a few times for some plausible values
pub(crate) static IOS_DEVICES: [&str; 5] = ["iPhone8,1", "iPhone11,1", "iPhone12,1", "iPhone13,1", "iPhone14,1"];
// Spoofed client for Android and iOS devices
#[derive(Debug, Clone, Default)]
pub(crate) struct Oauth {
// Currently unused, may be necessary if we decide to support GQL in the future
pub struct Oauth {
pub(crate) initial_headers: HashMap<String, String>,
pub(crate) headers_map: HashMap<String, String>,
pub(crate) token: String,
expires_in: u64,
@ -40,17 +27,19 @@ pub(crate) struct Oauth {
impl Oauth {
pub(crate) async fn new() -> Self {
let mut oauth = Oauth::default();
let mut oauth = Self::default();
oauth.login().await;
oauth
}
pub(crate) fn default() -> Self {
// Generate a random device to spoof
let device = Device::random();
let headers = device.headers.clone();
let headers_map = device.headers.clone();
let initial_headers = device.initial_headers.clone();
// For now, just insert headers - no token request
Oauth {
headers_map: headers,
Self {
headers_map,
initial_headers,
token: String::new(),
expires_in: 0,
device,
@ -62,16 +51,8 @@ impl Oauth {
let mut builder = Request::builder().method(Method::POST).uri(&url);
// Add headers from spoofed client
for (key, value) in self.headers_map.iter() {
// Skip Authorization header - won't be present in `Device` struct
// and will only be there in subsequent token refreshes.
// Sending a bearer auth token when requesting one is a bad idea
// Normally, you'd want to send it along to authenticate a refreshed token,
// but neither Android nor iOS does this - it just requests a new token.
// We try to match behavior as closely as possible.
if key != "Authorization" {
builder = builder.header(key, value);
}
for (key, value) in self.initial_headers.iter() {
builder = builder.header(key, value);
}
// Set up HTTP Basic Auth - basically just the const OAuth ID's with no password,
// Base64-encoded. https://en.wikipedia.org/wiki/Basic_access_authentication
@ -82,7 +63,7 @@ impl Oauth {
// Set JSON body. I couldn't tell you what this means. But that's what the client sends
let json = json!({
"scopes": ["*","email","pii"]
"scopes": ["*","email"]
});
let body = Body::from(json.to_string());
@ -100,6 +81,11 @@ impl Oauth {
self.headers_map.insert("x-reddit-loid".to_owned(), header.to_str().ok()?.to_string());
}
// Same with x-reddit-session
if let Some(header) = resp.headers().get("x-reddit-session") {
self.headers_map.insert("x-reddit-session".to_owned(), header.to_str().ok()?.to_string());
}
// Serialize response
let body_bytes = hyper::body::to_bytes(resp.into_body()).await.ok()?;
let json: serde_json::Value = serde_json::from_slice(&body_bytes).ok()?;
@ -109,7 +95,7 @@ impl Oauth {
self.expires_in = json.get("expires_in")?.as_u64()?;
self.headers_map.insert("Authorization".to_owned(), format!("Bearer {}", self.token));
info!("✅ Success - Retrieved token \"{}...\", expires in {}", &self.token[..32], self.expires_in);
info!("[] Success - Retrieved token \"{}...\", expires in {}", &self.token[..32], self.expires_in);
Some(())
}
@ -123,7 +109,7 @@ impl Oauth {
}
}
pub(crate) async fn token_daemon() {
pub async fn token_daemon() {
// Monitor for refreshing token
loop {
// Get expiry time - be sure to not hold the read lock
@ -132,22 +118,22 @@ pub(crate) async fn token_daemon() {
// sleep for the expiry time minus 2 minutes
let duration = Duration::from_secs(expires_in - 120);
info!("Waiting for {duration:?} seconds before refreshing OAuth token...");
info!("[⏳] Waiting for {duration:?} seconds before refreshing OAuth token...");
tokio::time::sleep(duration).await;
info!("[{duration:?} ELAPSED] Refreshing OAuth token...");
info!("[⌛] {duration:?} Elapsed! Refreshing OAuth token...");
// Refresh token - in its own scope
{
let mut client = OAUTH_CLIENT.write().await;
client.refresh().await;
OAUTH_CLIENT.write().await.refresh().await;
}
}
}
#[derive(Debug, Clone, Default)]
struct Device {
oauth_id: String,
initial_headers: HashMap<String, String>,
headers: HashMap<String, String>,
}
@ -156,8 +142,11 @@ impl Device {
// Generate uuid
let uuid = uuid::Uuid::new_v4().to_string();
// Select random user agent from ANDROID_USER_AGENT
let android_user_agent = choose(&ANDROID_USER_AGENT).to_string();
// Generate random user-agent
let android_app_version = choose(ANDROID_APP_VERSION_LIST).to_string();
let android_version = fastrand::u8(9..=14);
let android_user_agent = format!("Reddit/{android_app_version}/Android {android_version}");
// Android device headers
let headers = HashMap::from([
@ -166,61 +155,84 @@ impl Device {
("User-Agent".into(), android_user_agent),
]);
info!("Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
info!("[🔄] Spoofing Android client with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_ANDROID_OAUTH_CLIENT_ID}\"");
Device {
Self {
oauth_id: REDDIT_ANDROID_OAUTH_CLIENT_ID.to_string(),
headers,
headers: headers.clone(),
initial_headers: headers,
}
}
fn ios() -> Self {
// Generate uuid
let uuid = uuid::Uuid::new_v4().to_string();
// Select random user agent from IOS_USER_AGENT
let ios_user_agent = choose(&IOS_USER_AGENT).to_string();
// Generate random user-agent
let ios_app_version = choose(IOS_APP_VERSION_LIST).to_string();
let ios_os_version = choose(IOS_OS_VERSION_LIST).to_string();
let ios_user_agent = format!("Reddit/{ios_app_version}/iOS {ios_os_version}");
// Select random iOS device from IOS_DEVICES
let ios_device = choose(&IOS_DEVICES).to_string();
// Generate random device
let ios_device_num = fastrand::u8(8..=15).to_string();
let ios_device = format!("iPhone{ios_device_num},1").to_string();
// iOS device headers
let initial_headers = HashMap::from([
("X-Reddit-DPR".into(), "2".into()),
("User-Agent".into(), ios_user_agent.clone()),
("Device-Name".into(), ios_device.clone()),
]);
let headers = HashMap::from([
("X-Reddit-DPR".into(), "2".into()),
("Device-Name".into(), ios_device.clone()),
("X-Reddit-Device-Id".into(), uuid.clone()),
("User-Agent".into(), ios_user_agent),
("Client-Vendor-Id".into(), uuid.clone()),
("x-dev-ad-id".into(), "00000000-0000-0000-0000-000000000000".into()),
("Reddit-User_Id".into(), "anonymous_browsing_mode".into()),
("x-reddit-device-id".into(), uuid.clone()),
]);
info!("Spoofing iOS client {ios_device} with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_IOS_OAUTH_CLIENT_ID}\"");
info!("[🔄] Spoofing iOS client {ios_device} with headers: {headers:?}, uuid: \"{uuid}\", and OAuth ID \"{REDDIT_IOS_OAUTH_CLIENT_ID}\"");
Device {
Self {
oauth_id: REDDIT_IOS_OAUTH_CLIENT_ID.to_string(),
initial_headers,
headers,
}
}
// Randomly choose a device
fn random() -> Self {
if fastrand::bool() {
Device::android()
Self::android()
} else {
Device::ios()
Self::ios()
}
}
}
// Waiting on fastrand 2.0.0 for the `choose` function
// https://github.com/smol-rs/fastrand/pull/59/
fn choose<T: Copy>(list: &[T]) -> T {
*fastrand::choose_multiple(list.iter(), 1)[0]
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_oauth_client() {
assert!(!OAUTH_CLIENT.read().await.token.is_empty());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_oauth_client_refresh() {
OAUTH_CLIENT.write().await.refresh().await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_oauth_token_exists() {
assert!(!OAUTH_CLIENT.read().await.token.is_empty());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_oauth_headers_len() {
assert!(OAUTH_CLIENT.read().await.headers_map.len() >= 3);
}
#[test]
fn test_creating_device() {
Device::random();
}

231
src/oauth_resources.rs Normal file
View File

@ -0,0 +1,231 @@
pub static IOS_APP_VERSION_LIST: &[&str; 67] = &[
"Version 2020.0.0/Build 306960",
"Version 2020.10.0/Build 307041",
"Version 2020.10.1/Build 307047",
"Version 2020.1.0/Build 306966",
"Version 2020.11.0/Build 307049",
"Version 2020.11.1/Build 307063",
"Version 2020.12.0/Build 307070",
"Version 2020.13.0/Build 307072",
"Version 2020.13.1/Build 307075",
"Version 2020.14.0/Build 307077",
"Version 2020.14.1/Build 307080",
"Version 2020.15.0/Build 307084",
"Version 2020.16.0/Build 307090",
"Version 2020.17.0/Build 307093",
"Version 2020.19.0/Build 307137",
"Version 2020.20.0/Build 307156",
"Version 2020.20.1/Build 307159",
"Version 2020.2.0/Build 306969",
"Version 2020.21.0/Build 307162",
"Version 2020.21.1/Build 307165",
"Version 2020.22.0/Build 307177",
"Version 2020.22.1/Build 307181",
"Version 2020.23.0/Build 307183",
"Version 2020.24.0/Build 307189",
"Version 2020.25.0/Build 307198",
"Version 2020.26.0/Build 307205",
"Version 2020.26.1/Build 307213",
"Version 2020.27.0/Build 307229",
"Version 2020.28.0/Build 307233",
"Version 2020.29.0/Build 307235",
"Version 2020.30.0/Build 307238",
"Version 2020.3.0/Build 306971",
"Version 2020.31.0/Build 307240",
"Version 2020.31.1/Build 307246",
"Version 2020.32.0/Build 307250",
"Version 2020.33.0/Build 307252",
"Version 2020.34.0/Build 307260",
"Version 2020.35.0/Build 307262",
"Version 2020.36.0/Build 307265",
"Version 2020.37.0/Build 307272",
"Version 2020.38.0/Build 307286",
"Version 2020.39.0/Build 307306",
"Version 2020.4.0/Build 306978",
"Version 2020.5.0/Build 306993",
"Version 2020.5.1/Build 307005",
"Version 2020.6.0/Build 307007",
"Version 2020.7.0/Build 307012",
"Version 2020.8.0/Build 307014",
"Version 2020.8.1/Build 307017",
"Version 2020.9.0/Build 307035",
"Version 2020.9.1/Build 307039",
"Version 2023.18.0/Build 310494",
"Version 2023.19.0/Build 310507",
"Version 2023.20.0/Build 310535",
"Version 2023.21.0/Build 310560",
"Version 2023.22.0/Build 613580",
"Version 2023.23.0/Build 310613",
"Version 2023.23.1/Build 613639",
"Version 2023.24.0/Build 613663",
"Version 2023.25.0/Build 613739",
"Version 2023.26.0/Build 613749",
"Version 2023.27.0/Build 613771",
"Version 2023.28.0/Build 613803",
"Version 2023.28.1/Build 613809",
"Version 2023.29.0/Build 613825",
"Version 2023.30.0/Build 613849",
"Version 2023.31.0/Build 613864",
];
pub static ANDROID_APP_VERSION_LIST: &[&str; 150] = &[
"Version 2023.25.1/Build 1018737",
"Version 2023.26.0/Build 1019073",
"Version 2023.27.0/Build 1031923",
"Version 2023.28.0/Build 1046887",
"Version 2023.29.0/Build 1059855",
"Version 2023.30.0/Build 1078734",
"Version 2023.31.0/Build 1091027",
"Version 2023.32.0/Build 1109919",
"Version 2023.32.1/Build 1114141",
"Version 2023.33.1/Build 1129741",
"Version 2023.34.0/Build 1144243",
"Version 2023.35.0/Build 1157967",
"Version 2023.36.0/Build 1168982",
"Version 2023.37.0/Build 1182743",
"Version 2023.38.0/Build 1198522",
"Version 2023.39.0/Build 1211607",
"Version 2023.39.1/Build 1221505",
"Version 2023.40.0/Build 1221521",
"Version 2023.41.0/Build 1233125",
"Version 2023.41.1/Build 1239615",
"Version 2023.42.0/Build 1245088",
"Version 2023.43.0/Build 1257426",
"Version 2023.44.0/Build 1268622",
"Version 2023.45.0/Build 1281371",
"Version 2023.47.0/Build 1303604",
"Version 2023.48.0/Build 1319123",
"Version 2023.49.0/Build 1321715",
"Version 2023.49.1/Build 1322281",
"Version 2023.50.0/Build 1332338",
"Version 2023.50.1/Build 1345844",
"Version 2023.02.0/Build 717912",
"Version 2023.03.0/Build 729220",
"Version 2023.04.0/Build 744681",
"Version 2023.05.0/Build 755453",
"Version 2023.06.0/Build 775017",
"Version 2023.07.0/Build 788827",
"Version 2023.07.1/Build 790267",
"Version 2023.08.0/Build 798718",
"Version 2023.09.0/Build 812015",
"Version 2023.09.1/Build 816833",
"Version 2023.10.0/Build 821148",
"Version 2023.11.0/Build 830610",
"Version 2023.12.0/Build 841150",
"Version 2023.13.0/Build 852246",
"Version 2023.14.0/Build 861593",
"Version 2023.14.1/Build 864826",
"Version 2023.15.0/Build 870628",
"Version 2023.16.0/Build 883294",
"Version 2023.16.1/Build 886269",
"Version 2023.17.0/Build 896030",
"Version 2023.17.1/Build 900542",
"Version 2023.18.0/Build 911877",
"Version 2023.19.0/Build 927681",
"Version 2023.20.0/Build 943980",
"Version 2023.20.1/Build 946732",
"Version 2023.21.0/Build 956283",
"Version 2023.22.0/Build 968223",
"Version 2023.23.0/Build 983896",
"Version 2023.24.0/Build 998541",
"Version 2023.25.0/Build 1014750",
"Version 2022.24.0/Build 510950",
"Version 2022.24.1/Build 513462",
"Version 2022.25.0/Build 515072",
"Version 2022.25.1/Build 516394",
"Version 2022.25.2/Build 519915",
"Version 2022.26.0/Build 521193",
"Version 2022.27.0/Build 527406",
"Version 2022.27.1/Build 529687",
"Version 2022.28.0/Build 533235",
"Version 2022.30.0/Build 548620",
"Version 2022.31.0/Build 556666",
"Version 2022.31.1/Build 562612",
"Version 2022.32.0/Build 567875",
"Version 2022.33.0/Build 572600",
"Version 2022.34.0/Build 579352",
"Version 2022.35.0/Build 588016",
"Version 2022.35.1/Build 589034",
"Version 2022.36.0/Build 593102",
"Version 2022.37.0/Build 601691",
"Version 2022.38.0/Build 607460",
"Version 2022.39.0/Build 615385",
"Version 2022.39.1/Build 619019",
"Version 2022.40.0/Build 624782",
"Version 2022.41.0/Build 630468",
"Version 2022.41.1/Build 634168",
"Version 2022.42.0/Build 638508",
"Version 2022.43.0/Build 648277",
"Version 2022.44.0/Build 664348",
"Version 2022.45.0/Build 677985",
"Version 2023.01.0/Build 709875",
"Version 2021.45.0/Build 387663",
"Version 2021.46.0/Build 392043",
"Version 2021.47.0/Build 394342",
"Version 2022.10.0/Build 429896",
"Version 2022.1.0/Build 402829",
"Version 2022.11.0/Build 433004",
"Version 2022.12.0/Build 436848",
"Version 2022.13.0/Build 442084",
"Version 2022.13.1/Build 444621",
"Version 2022.14.1/Build 452742",
"Version 2022.15.0/Build 455453",
"Version 2022.16.0/Build 462377",
"Version 2022.17.0/Build 468480",
"Version 2022.18.0/Build 473740",
"Version 2022.19.1/Build 482464",
"Version 2022.20.0/Build 487703",
"Version 2022.2.0/Build 405543",
"Version 2022.21.0/Build 492436",
"Version 2022.22.0/Build 498700",
"Version 2022.23.0/Build 502374",
"Version 2022.23.1/Build 506606",
"Version 2022.3.0/Build 408637",
"Version 2022.4.0/Build 411368",
"Version 2022.5.0/Build 414731",
"Version 2022.6.0/Build 418391",
"Version 2022.6.1/Build 419585",
"Version 2022.6.2/Build 420562",
"Version 2022.7.0/Build 420849",
"Version 2022.8.0/Build 423906",
"Version 2022.9.0/Build 426592",
"Version 2021.17.0/Build 323213",
"Version 2021.18.0/Build 324849",
"Version 2021.19.0/Build 325762",
"Version 2021.20.0/Build 326964",
"Version 2021.21.0/Build 327703",
"Version 2021.21.1/Build 328461",
"Version 2021.22.0/Build 329696",
"Version 2021.23.0/Build 331631",
"Version 2021.24.0/Build 333951",
"Version 2021.25.0/Build 335451",
"Version 2021.26.0/Build 336739",
"Version 2021.27.0/Build 338857",
"Version 2021.28.0/Build 340747",
"Version 2021.29.0/Build 342342",
"Version 2021.30.0/Build 343820",
"Version 2021.31.0/Build 346485",
"Version 2021.32.0/Build 349507",
"Version 2021.33.0/Build 351843",
"Version 2021.34.0/Build 353911",
"Version 2021.35.0/Build 355878",
"Version 2021.36.0/Build 359254",
"Version 2021.36.1/Build 360572",
"Version 2021.37.0/Build 361905",
"Version 2021.38.0/Build 365032",
"Version 2021.39.0/Build 369068",
"Version 2021.39.1/Build 372418",
"Version 2021.41.0/Build 376052",
"Version 2021.42.0/Build 378193",
"Version 2021.43.0/Build 382019",
"Version 2021.44.0/Build 385129",
];
pub static IOS_OS_VERSION_LIST: &[&str; 8] = &[
"Version 17.0.1 (Build 21A340)",
"Version 17.0.2 (Build 21A350)",
"Version 17.0.3 (Build 21A360)",
"Version 17.1 (Build 21B74)",
"Version 17.1.1 (Build 21B91)",
"Version 17.1.2 (Build 21B101)",
"Version 17.2 (Build 21C62)",
"Version 17.2.1 (Build 21C66)",
];

View File

@ -47,11 +47,11 @@ impl CompressionType {
/// Returns a `CompressionType` given a content coding
/// in [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4)
/// format.
fn parse(s: &str) -> Option<CompressionType> {
fn parse(s: &str) -> Option<Self> {
let c = match s {
// Compressors we support.
"gzip" => CompressionType::Gzip,
"br" => CompressionType::Brotli,
"gzip" => Self::Gzip,
"br" => Self::Brotli,
// The wildcard means that we can choose whatever
// compression we prefer. In this case, use the
@ -69,8 +69,8 @@ impl CompressionType {
impl ToString for CompressionType {
fn to_string(&self) -> String {
match self {
CompressionType::Gzip => "gzip".to_string(),
CompressionType::Brotli => "br".to_string(),
Self::Gzip => "gzip".to_string(),
Self::Brotli => "br".to_string(),
_ => String::new(),
}
}
@ -195,7 +195,7 @@ impl Route<'_> {
impl Server {
pub fn new() -> Self {
Server {
Self {
default_headers: HeaderMap::new(),
router: Router::new(),
}

View File

@ -443,7 +443,7 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_fetching_subreddit() {
let subreddit = subreddit("rust", false).await;
assert!(subreddit.is_ok());

View File

@ -130,7 +130,7 @@ async fn user(name: &str) -> Result<User, String> {
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_fetching_user() {
let user = user("spez").await;
assert!(user.is_ok());