Skip to content

Commit

Permalink
Additional HTTP headers faking (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam committed Oct 4, 2022
1 parent c1f8a2b commit 89cc8e5
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 71 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver].

## UNRELEASED

### Added

- Additional HTTP headers (`Sec-CH-UA`, `Sec-CH-UA-Full-Version-List`, `Sec-CH-UA-Platform`, `Sec-CH-UA-Mobile`) faking [#348]

[#348]:https://github.com/tarampampam/random-user-agent/issues/348

## v3.9.0

### Changed
Expand Down
146 changes: 143 additions & 3 deletions src/hooks/before-send-headers.ts
Expand Up @@ -3,6 +3,7 @@ import Useragent from '../useragent/useragent'
import FilterService from '../services/filter-service'
import BlockingResponse = chrome.webRequest.BlockingResponse
import WebRequestHeadersDetails = chrome.webRequest.WebRequestHeadersDetails
import UseragentInfo from '../useragent/useragent-info'

export default class BeforeSendHeaders {
private readonly settings: Settings
Expand All @@ -20,17 +21,48 @@ export default class BeforeSendHeaders {
* @link https://stackoverflow.com/q/17567624/2252921 pass something to a content script
*/
listen(): void {
enum HeaderNames { // each should be in lower case
userAgent = 'user-agent',
secUa = 'sec-ch-ua', // https://mzl.la/3EaQyoi
secUaFullVersion = 'sec-ch-ua-full-version-list', // https://mzl.la/3C3x5TT
secUaPlatform = 'sec-ch-ua-platform', // https://mzl.la/3EbrbTj
secUaMobile = 'sec-ch-ua-mobile', // https://mzl.la/3SYTA3f
}

chrome.webRequest.onBeforeSendHeaders.addListener(
(details: WebRequestHeadersDetails): BlockingResponse | void => {
if (this.settings.get().enabled && this.filterService.applicableToURI(details.url)) {
const useragent = this.useragent.get().info

if (details.requestHeaders && useragent !== undefined) {
for (let i = 0; i < details.requestHeaders.length; i++) {
if (details.requestHeaders[i].name === 'User-Agent' && details.requestHeaders[i].value) {
details.requestHeaders[i].value = useragent.useragent
if (details.requestHeaders[i].value) {
switch (details.requestHeaders[i].name.toLowerCase()) {
// set faked user-agent
case HeaderNames.userAgent:
details.requestHeaders[i].value = useragent.useragent
break

// set faked user agent hint
case HeaderNames.secUa:
details.requestHeaders[i].value = this.fakeClientHintUserAgentHeader(useragent, false)
break

// set faked user agent full version hint
case HeaderNames.secUaFullVersion:
details.requestHeaders[i].value = this.fakeClientHintUserAgentHeader(useragent, true)
break

// set faked platform
case HeaderNames.secUaPlatform:
details.requestHeaders[i].value = '"' + this.fakePlatform(useragent) + '"'
break

break
// set faked user agent mobile hint
case HeaderNames.secUaMobile:
details.requestHeaders[i].value = this.fakeClientHintMobileHeader(useragent)
break
}
}
}

Expand All @@ -42,4 +74,112 @@ export default class BeforeSendHeaders {
['blocking', 'requestHeaders'],
)
}

/**
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Full-Version-List
*
* examples of Sec-CH-UA headers (Chromium, Chrome, Edge, and Opera):
* "(Not(A:Brand";v="8", "Chromium";v="98"
* " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
* " Not A;Brand";v="99", "Chromium";v="96", "Microsoft Edge";v="96"
* "Opera";v="81", " Not;A Brand";v="99", "Chromium";v="95"
*
* examples of Sec-CH-UA-Full-Version-List headers:
* " Not A;Brand";v="99.0.0.0", "Chromium";v="98.0.4750.0", "Google Chrome";v="98.0.4750.0"
*
* @private
*/
private fakeClientHintUserAgentHeader(info: UseragentInfo, fullVersion: boolean): string {
const parts: string[] = [ // for every case
// (in theory, for ff and safari we shouldn't give anything away, but...)
`"(Not(A:Brand";v="${fullVersion ? '99.0.0.0' : '99'}"`,
]

// for Chromium-based browsers only (firefox and safari has no support of "Sec-CH-UA"
// header - <https://caniuse.com/?search=Sec-CH-UA>)
if (info.engine === 'blink') {
const browserVersion: string = fullVersion
? info.browserVersion.full
: info.browserVersion.major.toString()

parts.push(`"Chromium";v="${browserVersion}"`)

if (info.browser === 'chrome') { // for Google Chrome
parts.push(`"Google Chrome";v="${browserVersion}"`)
}

if (info.brandBrowserVersion) {
const brandVersion: string = fullVersion
? info.brandBrowserVersion.full
: info.brandBrowserVersion.major.toString()

switch (info.browser) {
case 'edge':
parts.push(`"Microsoft Edge";v="${brandVersion}"`)
break

case 'opera':
parts.push(`"Opera";v="${brandVersion}"`)
break
}
}
}

return parts.join(', ')
}

/**
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/sec-ch-ua-platform#directives
*
* @private
*/
private fakePlatform(info: UseragentInfo): string {
enum UaPlatform {
Android = 'Android',
ChromeOS = 'Chrome OS',
ChromiumOS = 'Chromium OS',
iOS = 'iOS',
Linux = 'Linux',
macOS = 'macOS',
Windows = 'Windows',
Unknown = 'Unknown',
}

switch (info.osType) {
case 'windows':
return UaPlatform.Windows

case 'linux':
return UaPlatform.Linux

case 'macOS':
return UaPlatform.macOS

case 'iOS':
return UaPlatform.iOS

case 'android':
return UaPlatform.Android

default:
return UaPlatform.Unknown
}
}

/**
* @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA-Mobile
*
* @private
*/
private fakeClientHintMobileHeader(info: UseragentInfo): string {
switch (info.osType) {
case 'android':
case 'iOS':
return '?1'

default:
return '?0'
}
}
}
10 changes: 10 additions & 0 deletions src/services/useragent-service.ts
Expand Up @@ -92,6 +92,11 @@ export default class UseragentService {
useragent: random,
engine: 'unknown', // TODO probably detect this properties here?
osType: 'unknown',
browser: 'unknown',
browserVersion: {
major: 0,
full: '',
}
}
}
}
Expand All @@ -107,6 +112,11 @@ export default class UseragentService {
useragent: random,
engine: 'unknown', // TODO probably detect this properties here?
osType: 'unknown',
browser: 'unknown',
browserVersion: {
major: 0,
full: '',
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions src/useragent/generator.test.ts
Expand Up @@ -21,6 +21,7 @@ test('Generator.generate', async () => {
case GeneratorType.chromeLinux:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('linux')
expect(generated.browser).toBe('chrome')

expect(generated.useragent).toContain('X11')
expect(generated.useragent).toContain('Linux')
Expand All @@ -33,6 +34,7 @@ test('Generator.generate', async () => {
case GeneratorType.chromeMac:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('macOS')
expect(generated.browser).toBe('chrome')

expect(generated.useragent).toMatch(/Macintosh; Intel Mac OS X \d+_\d+/)
expect(generated.useragent).toMatch(/Chrome\/\d+\.0\.\d+\.\d+/)
Expand All @@ -44,6 +46,7 @@ test('Generator.generate', async () => {
case GeneratorType.chromeWin:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('windows')
expect(generated.browser).toBe('chrome')

expect(generated.useragent).toMatch(/Windows NT \d+.\d+/)
expect(generated.useragent).toContain('64')
Expand All @@ -56,6 +59,7 @@ test('Generator.generate', async () => {
case GeneratorType.chromeAndroid:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('android')
expect(generated.browser).toBe('chrome')

expect(generated.useragent).toMatch(/Linux; Android \d+/)
expect(generated.useragent).toMatch(/Chrome\/\d+\.0\.\d+\.\d+/)
Expand All @@ -68,6 +72,7 @@ test('Generator.generate', async () => {
case GeneratorType.firefoxLinux:
expect(generated.engine).toBe('gecko')
expect(generated.osType).toBe('linux')
expect(generated.browser).toBe('firefox')

expect(generated.useragent).toContain('X11')
expect(generated.useragent).toContain('Linux')
Expand All @@ -80,6 +85,7 @@ test('Generator.generate', async () => {
case GeneratorType.firefoxMac:
expect(generated.engine).toBe('gecko')
expect(generated.osType).toBe('macOS')
expect(generated.browser).toBe('firefox')

expect(generated.useragent).toMatch(/Macintosh; Intel Mac OS X \d+_\d+/)
expect(generated.useragent).toMatch(/Gecko\/\d+/)
Expand All @@ -91,6 +97,7 @@ test('Generator.generate', async () => {
case GeneratorType.firefoxWin:
expect(generated.engine).toBe('gecko')
expect(generated.osType).toBe('windows')
expect(generated.browser).toBe('firefox')

expect(generated.useragent).toMatch(/Windows NT \d+.\d+/)
expect(generated.useragent).toContain('64')
Expand All @@ -101,6 +108,7 @@ test('Generator.generate', async () => {
case GeneratorType.firefoxAndroid:
expect(generated.engine).toBe('gecko')
expect(generated.osType).toBe('android')
expect(generated.browser).toBe('firefox')

expect(generated.useragent).toContain('Android')
expect(generated.useragent).toMatch(/Firefox\/\d+\.0/)
Expand All @@ -112,6 +120,7 @@ test('Generator.generate', async () => {
case GeneratorType.operaWin:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('windows')
expect(generated.browser).toBe('opera')

expect(generated.useragent).toMatch(/Windows NT \d+.\d+/)
expect(generated.useragent).toContain('64')
Expand All @@ -125,6 +134,7 @@ test('Generator.generate', async () => {
case GeneratorType.operaMac:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('macOS')
expect(generated.browser).toBe('opera')

expect(generated.useragent).toMatch(/Macintosh; Intel Mac OS X \d+_\d+/)
expect(generated.useragent).toMatch(/Chrome\/\d+\.0\.\d+\.\d+/)
Expand All @@ -137,6 +147,7 @@ test('Generator.generate', async () => {
case GeneratorType.safariIphone:
expect(generated.engine).toBe('webkit')
expect(generated.osType).toBe('iOS')
expect(generated.browser).toBe('safari')

expect(generated.useragent).toMatch(/iPhone; CPU iPhone OS \d+_\d+ like Mac OS X/)
expect(generated.useragent).toMatch(/AppleWebKit\/\d+\.\d+/)
Expand All @@ -149,6 +160,7 @@ test('Generator.generate', async () => {
case GeneratorType.safariMac:
expect(generated.engine).toBe('webkit')
expect(generated.osType).toBe('macOS')
expect(generated.browser).toBe('safari')

expect(generated.useragent).toMatch(/Macintosh; Intel Mac OS X \d+_\d+(_\d+|)/)
expect(generated.useragent).toMatch(/AppleWebKit\/\d+\.\d+/)
Expand All @@ -161,6 +173,7 @@ test('Generator.generate', async () => {
case GeneratorType.edgeWin:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('windows')
expect(generated.browser).toBe('edge')

expect(generated.useragent).toMatch(/Windows NT \d+.\d+/)
expect(generated.useragent).toContain('64')
Expand All @@ -174,6 +187,7 @@ test('Generator.generate', async () => {
case GeneratorType.edgeMac:
expect(generated.engine).toBe('blink')
expect(generated.osType).toBe('macOS')
expect(generated.browser).toBe('edge')

expect(generated.useragent).toMatch(/Macintosh; Intel Mac OS X \d+_\d+/)
expect(generated.useragent).toMatch(/Chrome\/\d+\.0\.\d+\.\d+/)
Expand Down

0 comments on commit 89cc8e5

Please sign in to comment.