capi_param_builder_ruby 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/capi_param_builder.rb +67 -0
- data/lib/model/plain_data_object.rb +24 -0
- data/lib/release_config.rb +1 -1
- data/lib/util/request_context_adaptor.rb +170 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 666e5be3f9571422768ea730eb957935a5212dc0ede2d0cb2a98cee69b8acee0
|
|
4
|
+
data.tar.gz: a041d8fb610f9b6282ea7552225c8816930939f74237d1dfc6da25fb0cafeb43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eba9eaab35779653e37b304bacdaa41b198ad95c704e1f94c4670e3682577d9a546fd7a378f89c89b0270a20f7e4df450dcb81f1d78700922b9730d7fe908070
|
|
7
|
+
data.tar.gz: 4101a8e7fa1b721663c5368a42292174ed1cd7e59c5bf6ddec5f5eab6f552d004150417f4ff4636e810825e83cea4bb56fc1fe1723e9cdbabad95c1501770cda
|
data/lib/capi_param_builder.rb
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
require_relative 'model/fbc_param_configs'
|
|
8
8
|
require_relative 'model/cookie_settings'
|
|
9
9
|
require_relative 'model/etld_plus_one_resolver'
|
|
10
|
+
require_relative 'model/plain_data_object'
|
|
11
|
+
require_relative 'util/request_context_adaptor'
|
|
10
12
|
require_relative 'release_config'
|
|
11
13
|
require 'set'
|
|
12
14
|
require 'uri'
|
|
@@ -40,6 +42,8 @@ class ParamBuilder
|
|
|
40
42
|
@appendix_net_new = get_appendix(APPENDIX_NET_NEW)
|
|
41
43
|
@appendix_modified_new = get_appendix(APPENDIX_MODIFIED_NEW)
|
|
42
44
|
@appendix_no_change = get_appendix(APPENDIX_NO_CHANGE)
|
|
45
|
+
@referrer_url = nil
|
|
46
|
+
@event_source_url = nil
|
|
43
47
|
|
|
44
48
|
if input.nil?
|
|
45
49
|
return
|
|
@@ -175,12 +179,18 @@ class ParamBuilder
|
|
|
175
179
|
end
|
|
176
180
|
|
|
177
181
|
def process_request(host, queries, cookies, referer=nil)
|
|
182
|
+
@event_source_url = nil
|
|
178
183
|
compute_etld_plus_one_for_host(host)
|
|
179
184
|
@cookie_to_set_dict = {}
|
|
180
185
|
@cookie_to_set = Set.new()
|
|
181
186
|
@fbc = pre_process_cookies(cookies, FBC_NAME)
|
|
182
187
|
@fbp = pre_process_cookies(cookies, FBP_NAME)
|
|
183
188
|
|
|
189
|
+
@referrer_url = referer
|
|
190
|
+
if @referrer_url.is_a?(String) && !@referrer_url.empty?
|
|
191
|
+
@referrer_url = "#{@referrer_url}.#{@appendix_no_change}"
|
|
192
|
+
end
|
|
193
|
+
|
|
184
194
|
# Get new fbc payload
|
|
185
195
|
new_fbc_payload = get_new_fbc_payload_from_url(queries, referer)
|
|
186
196
|
|
|
@@ -200,6 +210,32 @@ class ParamBuilder
|
|
|
200
210
|
return @cookie_to_set
|
|
201
211
|
end
|
|
202
212
|
|
|
213
|
+
# Process a request from a context object.
|
|
214
|
+
#
|
|
215
|
+
# Accepts either a PlainDataObject (used directly) or any framework
|
|
216
|
+
# request / Rack-style env Hash that RequestContextAdaptor knows how to
|
|
217
|
+
# extract from. Nil falls through to the adapter's empty-default behavior.
|
|
218
|
+
#
|
|
219
|
+
# Note: PlainDataObject carries x_forwarded_for and remote_address for
|
|
220
|
+
# parity with the JS / PHP SDKs, but the Ruby ParamBuilder does not yet
|
|
221
|
+
# implement client-IP attribution; those fields are extracted by the
|
|
222
|
+
# adapter but ignored here.
|
|
223
|
+
def process_request_from_context(context = nil)
|
|
224
|
+
data = context.is_a?(PlainDataObject) ?
|
|
225
|
+
context : RequestContextAdaptor.extract(context)
|
|
226
|
+
|
|
227
|
+
process_request(
|
|
228
|
+
data.host,
|
|
229
|
+
data.query_params,
|
|
230
|
+
data.cookies,
|
|
231
|
+
data.referer
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
@event_source_url = construct_event_source_url(data)
|
|
235
|
+
|
|
236
|
+
return @cookie_to_set
|
|
237
|
+
end
|
|
238
|
+
|
|
203
239
|
def get_cookies_to_set()
|
|
204
240
|
return @cookie_to_set
|
|
205
241
|
end
|
|
@@ -212,9 +248,25 @@ class ParamBuilder
|
|
|
212
248
|
return @fbp
|
|
213
249
|
end
|
|
214
250
|
|
|
251
|
+
def get_referrer_url()
|
|
252
|
+
return @referrer_url
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def get_event_source_url()
|
|
256
|
+
return @event_source_url
|
|
257
|
+
end
|
|
258
|
+
|
|
215
259
|
private def compute_etld_plus_one_for_host(host)
|
|
216
260
|
if @etld_plus_one.nil? || @host.nil?
|
|
217
261
|
@host = host
|
|
262
|
+
# Guard empty/nil host: Ruby's "".split(".") returns [], so naively
|
|
263
|
+
# computing size - 1 would yield -1 and emit malformed `fb.-1.…`
|
|
264
|
+
# cookies. Match Python's behavior: empty host -> nil etld+1, index 0.
|
|
265
|
+
if host.nil? || host.empty?
|
|
266
|
+
@etld_plus_one = nil
|
|
267
|
+
@sub_domain_index = 0
|
|
268
|
+
return
|
|
269
|
+
end
|
|
218
270
|
host_name = extract_host_from_http_host(host)
|
|
219
271
|
if is_ip_address(host_name)
|
|
220
272
|
@etld_plus_one = maybe_bracket_ipv6(host_name)
|
|
@@ -286,6 +338,21 @@ class ParamBuilder
|
|
|
286
338
|
return host_name
|
|
287
339
|
end
|
|
288
340
|
|
|
341
|
+
private def construct_event_source_url(data)
|
|
342
|
+
return nil if data.nil?
|
|
343
|
+
return nil if data.host.nil? || data.host.empty?
|
|
344
|
+
return nil if data.scheme.nil? || data.scheme.empty?
|
|
345
|
+
|
|
346
|
+
url = "#{data.scheme.downcase}://#{data.host}"
|
|
347
|
+
if !data.request_uri.nil? && !data.request_uri.empty?
|
|
348
|
+
url += data.request_uri
|
|
349
|
+
end
|
|
350
|
+
if url.is_a?(String) && !url.empty?
|
|
351
|
+
url = "#{url}.#{@appendix_net_new}"
|
|
352
|
+
end
|
|
353
|
+
url
|
|
354
|
+
end
|
|
355
|
+
|
|
289
356
|
private def get_updated_fbc_cookie(existing_fbc = nil, new_fbc_payload)
|
|
290
357
|
if @fbc_params_configs.nil?
|
|
291
358
|
return nil
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# This source code is licensed under the license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
class PlainDataObject
|
|
8
|
+
attr_accessor :host, :query_params, :cookies, :referer,
|
|
9
|
+
:x_forwarded_for, :remote_address, :scheme,
|
|
10
|
+
:request_uri
|
|
11
|
+
|
|
12
|
+
def initialize(host, query_params, cookies, referer = nil,
|
|
13
|
+
x_forwarded_for = nil, remote_address = nil,
|
|
14
|
+
scheme = nil, request_uri = nil)
|
|
15
|
+
@host = host
|
|
16
|
+
@query_params = query_params
|
|
17
|
+
@cookies = cookies
|
|
18
|
+
@referer = referer
|
|
19
|
+
@x_forwarded_for = x_forwarded_for
|
|
20
|
+
@remote_address = remote_address
|
|
21
|
+
@scheme = scheme
|
|
22
|
+
@request_uri = request_uri
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/release_config.rb
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# This source code is licensed under the license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
require 'cgi'
|
|
8
|
+
require_relative '../model/plain_data_object'
|
|
9
|
+
|
|
10
|
+
# Universal Request Context Adaptor for Ruby.
|
|
11
|
+
#
|
|
12
|
+
# Extracts request data (host, query params, cookies, referer,
|
|
13
|
+
# x-forwarded-for, remote address) from a Rack-style environ hash or any
|
|
14
|
+
# framework request object that exposes #env (Rack, Rails, Sinatra). Falls
|
|
15
|
+
# back to empty defaults for nil or unrecognized inputs.
|
|
16
|
+
class RequestContextAdaptor
|
|
17
|
+
HTTP_DEFAULT_PORT = 80
|
|
18
|
+
HTTPS_DEFAULT_PORT = 443
|
|
19
|
+
|
|
20
|
+
def self.extract(request_obj = nil)
|
|
21
|
+
host = ''
|
|
22
|
+
query_params = {}
|
|
23
|
+
cookies = {}
|
|
24
|
+
referer = nil
|
|
25
|
+
x_forwarded_for = nil
|
|
26
|
+
remote_address = nil
|
|
27
|
+
scheme = nil
|
|
28
|
+
request_uri = nil
|
|
29
|
+
|
|
30
|
+
if request_obj.nil?
|
|
31
|
+
return PlainDataObject.new(
|
|
32
|
+
host, query_params, cookies, referer, x_forwarded_for, remote_address,
|
|
33
|
+
scheme, request_uri
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
env = resolve_env(request_obj)
|
|
39
|
+
if env.is_a?(Hash) && !env.empty?
|
|
40
|
+
host = wsgi_host(env)
|
|
41
|
+
referer = nilify(env['HTTP_REFERER'])
|
|
42
|
+
x_forwarded_for = nilify(env['HTTP_X_FORWARDED_FOR'])
|
|
43
|
+
remote_address = nilify(env['REMOTE_ADDR'])
|
|
44
|
+
|
|
45
|
+
query_params = parse_query_string(env['QUERY_STRING'])
|
|
46
|
+
cookies = parse_cookie_header(env['HTTP_COOKIE'])
|
|
47
|
+
|
|
48
|
+
scheme = extract_scheme(env)
|
|
49
|
+
request_uri = extract_request_uri(env)
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError
|
|
52
|
+
# Silently swallow exceptions and return the object with default values.
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
PlainDataObject.new(
|
|
56
|
+
host, query_params, cookies, referer, x_forwarded_for, remote_address,
|
|
57
|
+
scheme, request_uri
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.resolve_env(request_obj)
|
|
62
|
+
if request_obj.respond_to?(:env) && request_obj.env.is_a?(Hash)
|
|
63
|
+
return request_obj.env
|
|
64
|
+
end
|
|
65
|
+
return request_obj if request_obj.is_a?(Hash)
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
private_class_method :resolve_env
|
|
69
|
+
|
|
70
|
+
def self.parse_query_string(query_string)
|
|
71
|
+
return {} if query_string.nil? || query_string.to_s.empty?
|
|
72
|
+
CGI.parse(query_string.to_s)
|
|
73
|
+
end
|
|
74
|
+
private_class_method :parse_query_string
|
|
75
|
+
|
|
76
|
+
def self.parse_cookie_header(raw_cookie)
|
|
77
|
+
return {} if raw_cookie.nil? || raw_cookie.to_s.empty?
|
|
78
|
+
raw_cookie.to_s.split(';').each_with_object({}) do |pair, hash|
|
|
79
|
+
parts = pair.split('=', 2)
|
|
80
|
+
next unless parts.size == 2
|
|
81
|
+
key = parts[0].strip
|
|
82
|
+
next if key.empty?
|
|
83
|
+
begin
|
|
84
|
+
hash[key] = percent_decode(parts[1].strip)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
# Per-pair isolation: a single bad cookie (e.g. an encoding error)
|
|
87
|
+
# must not drop the other valid cookies in the same header.
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
private_class_method :parse_cookie_header
|
|
92
|
+
|
|
93
|
+
# Percent-decode a cookie value WITHOUT converting `+` to space. CGI.unescape
|
|
94
|
+
# applies form decoding (`+` -> ` `), which would corrupt base64 / JWT-like
|
|
95
|
+
# cookie values that legitimately contain `+`.
|
|
96
|
+
#
|
|
97
|
+
# Operates on a BINARY copy first so that gsub-ing ASCII-8BIT bytes (from
|
|
98
|
+
# `pack("H2")`) into a string that already carries UTF-8 multi-byte
|
|
99
|
+
# characters does not raise `Encoding::CompatibilityError`. After decoding
|
|
100
|
+
# we relabel as UTF-8 and `scrub` any invalid byte sequences (e.g. lone
|
|
101
|
+
# `%FF`) so downstream JSON / logging / hashing does not choke on
|
|
102
|
+
# invalid UTF-8.
|
|
103
|
+
def self.percent_decode(value)
|
|
104
|
+
value.to_s.b
|
|
105
|
+
.gsub(/%([0-9a-fA-F]{2})/) { [Regexp.last_match(1)].pack('H2') }
|
|
106
|
+
.force_encoding(Encoding::UTF_8)
|
|
107
|
+
.scrub
|
|
108
|
+
end
|
|
109
|
+
private_class_method :percent_decode
|
|
110
|
+
|
|
111
|
+
def self.wsgi_host(env)
|
|
112
|
+
host = env['HTTP_HOST']
|
|
113
|
+
return host if host && !host.empty?
|
|
114
|
+
server_name = env['SERVER_NAME']
|
|
115
|
+
return '' if server_name.nil? || server_name.empty?
|
|
116
|
+
server_port = env['SERVER_PORT']
|
|
117
|
+
scheme = env['rack.url_scheme'] || 'http'
|
|
118
|
+
format_host_port(server_name, server_port, scheme)
|
|
119
|
+
end
|
|
120
|
+
private_class_method :wsgi_host
|
|
121
|
+
|
|
122
|
+
# Build a host[:port] authority, bracketing bare IPv6 literals. We always
|
|
123
|
+
# bracket bare IPv6 — even when the port is omitted — so that downstream
|
|
124
|
+
# `extract_host_from_http_host` (which treats the last `:` as a port
|
|
125
|
+
# separator when no `]` is present) does not truncate the address.
|
|
126
|
+
def self.format_host_port(host, port, scheme)
|
|
127
|
+
bracketed = host.include?(':') && !host.start_with?('[') ? "[#{host}]" : host
|
|
128
|
+
default_port =
|
|
129
|
+
scheme == 'https' || scheme == 'wss' ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT
|
|
130
|
+
if port.nil? || port.to_s.empty? || port.to_s == default_port.to_s
|
|
131
|
+
return bracketed
|
|
132
|
+
end
|
|
133
|
+
"#{bracketed}:#{port}"
|
|
134
|
+
end
|
|
135
|
+
private_class_method :format_host_port
|
|
136
|
+
|
|
137
|
+
def self.extract_scheme(env)
|
|
138
|
+
raw_str = (env['REQUEST_SCHEME'] || env['rack.url_scheme']).to_s
|
|
139
|
+
return raw_str.downcase unless raw_str.empty?
|
|
140
|
+
|
|
141
|
+
https_str = env['HTTPS'].to_s
|
|
142
|
+
return 'https' if !https_str.empty? && https_str.downcase != 'off'
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
private_class_method :extract_scheme
|
|
146
|
+
|
|
147
|
+
def self.extract_request_uri(env)
|
|
148
|
+
raw = env['REQUEST_URI']
|
|
149
|
+
return raw.to_s if raw && !raw.to_s.empty?
|
|
150
|
+
|
|
151
|
+
script_name = env['SCRIPT_NAME'].to_s
|
|
152
|
+
path_info = env['PATH_INFO'].to_s
|
|
153
|
+
uri = script_name + path_info
|
|
154
|
+
query_string = env['QUERY_STRING']
|
|
155
|
+
has_qs = query_string && !query_string.to_s.empty?
|
|
156
|
+
|
|
157
|
+
return nil if uri.empty? && !has_qs
|
|
158
|
+
uri = '/' if uri.empty?
|
|
159
|
+
uri = "#{uri}?#{query_string}" if has_qs
|
|
160
|
+
uri
|
|
161
|
+
end
|
|
162
|
+
private_class_method :extract_request_uri
|
|
163
|
+
|
|
164
|
+
def self.nilify(value)
|
|
165
|
+
return nil if value.nil?
|
|
166
|
+
return nil if value.respond_to?(:empty?) && value.empty?
|
|
167
|
+
value
|
|
168
|
+
end
|
|
169
|
+
private_class_method :nilify
|
|
170
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capi_param_builder_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Facebook
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|
|
@@ -20,7 +20,9 @@ files:
|
|
|
20
20
|
- lib/model/cookie_settings.rb
|
|
21
21
|
- lib/model/etld_plus_one_resolver.rb
|
|
22
22
|
- lib/model/fbc_param_configs.rb
|
|
23
|
+
- lib/model/plain_data_object.rb
|
|
23
24
|
- lib/release_config.rb
|
|
25
|
+
- lib/util/request_context_adaptor.rb
|
|
24
26
|
homepage:
|
|
25
27
|
licenses:
|
|
26
28
|
- Nonstandard
|