python-ipware 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl

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.
@@ -1 +1 @@
1
- __version__ = "2.0.3"
1
+ __version__ = "2.0.5"
@@ -1,7 +1,7 @@
1
1
  import ipaddress
2
2
  import logging
3
3
 
4
- from typing import Any, Dict, List, Optional, Tuple, Union
4
+ from typing import Dict, List, Optional, Tuple, Union
5
5
 
6
6
  IpAddressType = Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
7
7
  OptionalIpAddressType = Optional[IpAddressType]
@@ -11,125 +11,130 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
  class IpWareMeta:
13
13
  """
14
- A class that handles meta data frm an HTTP request.
14
+ A class to manage metadata extracted from HTTP request headers, primarily for identifying
15
+ the client's IP address behind load balancers and proxies.
15
16
  """
16
17
 
17
18
  def __init__(
18
- self,
19
- precedence: Optional[Tuple[str, ...]] = None,
20
- leftmost: bool = True,
19
+ self, precedence: Optional[Tuple[str, ...]] = None, leftmost: bool = True
21
20
  ) -> None:
22
- self.precedence = precedence or (
23
- "X_FORWARDED_FOR", # Load balancers or proxies such as AWS ELB (default client is `left-most` [`<client>, <proxy1>, <proxy2>`])
24
- "HTTP_X_FORWARDED_FOR", # Similar to X_FORWARDED_TO
25
- "HTTP_CLIENT_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
26
- "HTTP_X_REAL_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
27
- "HTTP_X_FORWARDED", # Squid and others
28
- "HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
29
- "HTTP_FORWARDED_FOR", # RFC 7239
30
- "HTTP_FORWARDED", # RFC 7239
31
- "HTTP_CF_CONNECTING_IP", # CloudFlare
32
- "X-CLIENT-IP", # Microsoft Azure
33
- "X-REAL-IP", # NGINX
34
- "X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
35
- "X_FORWARDED", # Squid
36
- "FORWARDED_FOR", # RFC 7239
37
- "CF-CONNECTING-IP", # CloudFlare
38
- "TRUE-CLIENT-IP", # CloudFlare Enterprise,
39
- "FASTLY-CLIENT-IP", # Firebase, Fastly
40
- "FORWARDED", # RFC 7239
41
- "CLIENT-IP", # Akamai and Cloudflare: True-Client-IP and Fastly: Fastly-Client-IP
42
- "REMOTE_ADDR", # Default
43
- )
21
+ """
22
+ Initializes the metadata handler with a precedence list of HTTP headers and the position of the client IP.
23
+
24
+ :param precedence: A tuple of header names that define the order to check for a valid IP.
25
+ If not provided, a default list of commonly used headers is applied.
26
+ :param leftmost: A boolean that indicates whether to use the left-most IP in the header
27
+ when multiple IPs are listed (typically the case with proxies).
28
+ """
29
+ # Default precedence list of headers if none is provided
30
+ if precedence is None:
31
+ precedence = (
32
+ "X_FORWARDED_FOR", # Common header for proxies, load balancers, like AWS ELB. Default to the left-most IP.
33
+ "HTTP_X_FORWARDED_FOR", # Alternative header similar to `X_FORWARDED_FOR`.
34
+ "HTTP_CLIENT_IP", # Header used by some providers like Amazon EC2, Heroku.
35
+ "HTTP_X_REAL_IP", # Header used by some providers like Amazon EC2, Heroku.
36
+ "HTTP_X_FORWARDED", # Used by Squid and similar software.
37
+ "HTTP_X_CLUSTER_CLIENT_IP", # Used by Rackspace LB, Riverbed Stingray.
38
+ "HTTP_FORWARDED_FOR", # Standard header defined by RFC 7239.
39
+ "HTTP_FORWARDED", # Standard header defined by RFC 7239.
40
+ "HTTP_CF_CONNECTING_IP", # Used by CloudFlare.
41
+ "X-CLIENT-IP", # Used by Microsoft Azure.
42
+ "X-REAL-IP", # Commonly used by NGINX.
43
+ "X-CLUSTER-CLIENT-IP", # Used by Rackspace Cloud Load Balancers.
44
+ "X_FORWARDED", # Used by Squid.
45
+ "FORWARDED_FOR", # Standard header defined by RFC 7239.
46
+ "CF-CONNECTING-IP", # Used by CloudFlare.
47
+ "TRUE-CLIENT-IP", # Header for CloudFlare Enterprise.
48
+ "FASTLY-CLIENT-IP", # Used by Fastly, Firebase.
49
+ "FORWARDED", # Standard header defined by RFC 7239.
50
+ "CLIENT-IP", # Used by Akamai, Cloudflare's True-Client-IP, and Fastly's Fastly-Client-IP.
51
+ "REMOTE_ADDR", # The default IP address header (direct connection).
52
+ )
53
+
54
+ self.precedence = precedence
44
55
  self.leftmost = leftmost
45
56
 
46
57
 
47
58
  class IpWareIpAddress:
48
59
  """
49
- A class that handles IP address data from an HTTP request.
60
+ A class for handling and parsing IP address data from HTTP requests.
50
61
  """
51
62
 
52
- def extract_ipv4_only(self, ip_address: str) -> str:
53
- """
54
- Given an IPv4 address or address:port, it extracts the IP address
55
- @param ip_str: IP address or address:port
56
- @return: IP address
63
+ def extract_ipv4(self, ip_address: str) -> str:
57
64
  """
65
+ Extracts the IPv4 address from a given string that may include a port number.
58
66
 
59
- if ip_address:
60
- # handle ipv4 addresses with port
61
- if ":" in ip_address:
62
- ip_address = ip_address.split(":")[0]
63
- return ip_address.strip()
64
-
65
- return ip_address.strip()
67
+ Args:
68
+ ip_address (str): An IPv4 address possibly including a port (e.g., "192.168.1.1:8080").
66
69
 
67
- return ""
68
-
69
- def extract_ipv6_only(self, ip_address: str) -> str:
70
- """
71
- Given an IPv6 address or address:port, it extracts the IP address
72
- @param ip_str: IP address or address:port
73
- @return: IP address
70
+ Returns:
71
+ str: The IPv4 address without the port. Returns an empty string if input is None.
74
72
  """
75
-
76
73
  if ip_address:
77
- # handle ipv6 addresses with port
78
- if "]:" in ip_address:
79
- ip_address = ip_address.split("]:")[0]
80
- ip_address = ip_address.replace("[", "")
81
- return ip_address.strip()
74
+ return ip_address.split(":")[0].strip()
75
+ return ""
82
76
 
83
- return ip_address.strip()
77
+ def extract_ipv6(self, ip_address: str) -> str:
78
+ """
79
+ Extracts the IPv6 address from a given string that may include a port number.
84
80
 
85
- return ""
81
+ Args:
82
+ ip_address (str): An IPv6 address possibly including a port (e.g., "[2001:db8::1]:8080").
86
83
 
87
- def get_ip_object(
88
- self,
89
- ip_str: str,
90
- ) -> OptionalIpAddressType:
84
+ Returns:
85
+ str: The IPv6 address without brackets or port. Returns an empty string if input is None.
91
86
  """
92
- Given an IP address or address:port, it parses the IP address
93
- @param ip_str: IP address or address:port
94
- @return: IP address of type IPv4Address or IPv6Address
87
+ if ip_address and "]:" in ip_address:
88
+ return ip_address.split("]:")[0].replace("[", "").strip()
89
+ return ip_address.strip() if ip_address else ""
90
+
91
+ def parse_ip_address(self, ip_str: str):
95
92
  """
93
+ Parses the given IP address string to an IPv4Address or IPv6Address object.
94
+
95
+ Args:
96
+ ip_str (str): An IP address possibly including a port.
96
97
 
97
- ip: OptionalIpAddressType = None
98
- if ip_str:
98
+ Returns:
99
+ ipaddress.IPv4Address|ipaddress.IPv6Address|None: The parsed IP object or None if the address is invalid.
100
+ """
101
+ try:
102
+ # First, try to parse as IPv6.
103
+ ipv6 = self.extract_ipv6(ip_str)
104
+ ip = ipaddress.IPv6Address(ipv6)
105
+ return ip.ipv4_mapped or ip
106
+ except ipaddress.AddressValueError:
99
107
  try:
100
- # try to parse as IPv6 address with optional port
101
- ipv6 = self.extract_ipv6_only(ip_str)
102
- ip = ipaddress.IPv6Address(ipv6)
103
- ip = ip.ipv4_mapped or ip
108
+ # If IPv6 parsing fails, try to parse as IPv4.
109
+ ipv4 = self.extract_ipv4(ip_str)
110
+ return ipaddress.IPv4Address(ipv4)
104
111
  except ipaddress.AddressValueError:
105
- try:
106
- # try to parse as IPv4 address with optional port
107
- ipv4 = self.extract_ipv4_only(ip_str)
108
- ip = ipaddress.IPv4Address(ipv4)
109
- except ipaddress.AddressValueError:
110
- # not a valid IP address, return None
111
- logger.info("Invalid ip address. {0}".format(ip_str))
112
- ip = None
113
- return ip
112
+ # Log error if IP is invalid
113
+ # print(f"Invalid IP address: {ip_str}")
114
+ return None
114
115
 
115
116
  def get_ips_from_string(
116
117
  self,
117
118
  ip_str: str,
118
- ) -> Optional[List[IpAddressType]]:
119
- """
120
- Given a comma separated list of IP addresses or address:port, it parses the IP addresses
121
- @param ip_str: comma separated list of IP addresses or address:port
122
- @return: list of IP addresses of type IPv4Address or IPv6Address
119
+ strict: bool = False,
120
+ ) -> Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]]:
123
121
  """
124
- ip_list: List[IpAddressType] = []
122
+ Parses a comma-separated list of IP addresses, each possibly including a port.
125
123
 
124
+ Args:
125
+ ip_str (str): A comma-separated list of IP addresses or address:port entries.
126
+ strict (bool): True to bail out on first invalid ip, False to skip invalid ip
127
+ Returns:
128
+ List[ipaddress.IPv4Address|ipaddress.IPv6Address]|None: A list of IP address objects or None if any IP is invalid.
129
+ """
130
+ ip_list = []
126
131
  for ip_address in ip_str.split(","):
127
- ip = self.get_ip_object(ip_address.strip())
132
+ ip = self.parse_ip_address(ip_address.strip())
128
133
  if ip:
129
134
  ip_list.append(ip)
130
135
  else:
131
- # we have at least one invalid IP address, return empty list, instead
132
- return None
136
+ if strict:
137
+ return None
133
138
 
134
139
  if not self.leftmost:
135
140
  ip_list.reverse()
@@ -139,125 +144,133 @@ class IpWareIpAddress:
139
144
 
140
145
  class IpWareProxy:
141
146
  """
142
- A class that handles proxy data from an HTTP request.
147
+ A class to handle proxy data from an HTTP request, including validating
148
+ proxy counts and trusted proxy lists.
143
149
  """
144
150
 
145
151
  def __init__(
146
- self,
147
- proxy_count: int = 0,
148
- proxy_list: Optional[List[str]] = None,
152
+ self, proxy_count: Optional[int] = None, proxy_list: Optional[List[str]] = None
149
153
  ) -> None:
150
- if proxy_count is None or proxy_count < 0:
151
- raise ValueError("proxy_count must be a positive integer")
154
+ """
155
+ Initialize the IpWareProxy class with optional proxy count and proxy list.
152
156
 
157
+ Args:
158
+ proxy_count: The expected number of proxies, can be None if no specific count is enforced.
159
+ proxy_list: A list of partial IP addresses as trusted proxies, can be None.
160
+ """
161
+ if proxy_count is not None and proxy_count < 0:
162
+ raise ValueError("proxy_count must be non-negative")
153
163
  self.proxy_count = proxy_count
154
- self.proxy_list = self._is_valid_proxy_trusted_list(proxy_list or [])
164
+ self.proxy_list = self._validate_proxy_list(proxy_list or [])
155
165
 
156
- def _is_valid_proxy_trusted_list(self, proxy_list: Any) -> List[str]:
157
- """
158
- Checks if the proxy list is a valid list of strings
159
- @return: proxy list or raises an exception
166
+ def _validate_proxy_list(self, proxy_list: List[str]) -> List[str]:
160
167
  """
168
+ Validates that the proxy list contains only strings.
161
169
 
162
- if not isinstance(proxy_list, list):
163
- raise ValueError("Parameter must be a list")
164
- if not all(isinstance(x, str) for x in proxy_list):
165
- raise ValueError("All elements in list must be strings")
170
+ Args:
171
+ proxy_list: A list of proxies.
166
172
 
167
- return proxy_list
173
+ Returns:
174
+ The proxy list if all items are valid strings.
168
175
 
169
- def is_proxy_count_valid(
170
- self, ip_list: List[IpAddressType], strict: bool = False
171
- ) -> bool:
176
+ Raises:
177
+ ValueError: If the proxy list is not a list of strings.
172
178
  """
173
- Checks if the proxy count is valid
174
- @param ip_list: list of ip addresses
175
- @param strict: if True, we must have exactly proxy_count proxies
176
- @return: True if the proxy count is valid, False otherwise
179
+ if not all(isinstance(ip, str) for ip in proxy_list):
180
+ raise ValueError("All elements in the proxy list must be strings.")
181
+ return proxy_list
182
+
183
+ def is_proxy_count_valid(self, ip_list: List[str], strict: bool = False) -> bool:
177
184
  """
178
- if self.proxy_count < 1:
179
- return True
185
+ Validates the proxy count against a list of IP addresses.
180
186
 
181
- ip_count: int = len(ip_list)
182
- if ip_count < 1:
183
- return False
187
+ Args:
188
+ ip_list: A list of IP addresses from the request headers.
189
+ strict: If True, the number of proxies must exactly match the proxy_count.
184
190
 
191
+ Returns:
192
+ True if the proxy count is valid, False otherwise.
193
+ """
194
+ if self.proxy_count is None:
195
+ return True # No proxy count specified, so always valid.
196
+
197
+ ip_count = len(ip_list)
185
198
  if strict:
186
- # our first proxy takes the last ip address and treats it as client ip
187
- return self.proxy_count == ip_count - 1
199
+ return ip_count - 1 == self.proxy_count
200
+ # Exact match required, excluding client's own IP.
188
201
 
189
- # the client could have gone through their own proxy and included extra ips
190
- # client could be sending in the header: X-Forwarded-For: <fake_ip>, <client_ip>
191
- return ip_count - 1 > self.proxy_count
202
+ return ip_count - 1 >= self.proxy_count
203
+ # Allow more proxies than the count, excluding client's IP.
192
204
 
193
205
  def is_proxy_trusted_list_valid(
194
- self,
195
- ip_list: List[IpAddressType],
196
- strict: bool = False,
206
+ self, ip_list: List[str], strict: bool = False
197
207
  ) -> bool:
198
208
  """
199
- Checks if the proxy list is valid (all proxies are in the proxy_list)
200
- @param ip_list: list of ip addresses
201
- @param strict: if True, we must have exactly proxy_count proxies
202
- @return: client's best match ip address or False
209
+ Checks if all proxies in the incoming list are trusted based on the proxy_list.
210
+
211
+ Args:
212
+ ip_list: A list of IP addresses from the request headers.
213
+ strict: If True, the number of proxies must exactly match the length of proxy_list.
214
+
215
+ Returns:
216
+ True if all proxies are trusted, False otherwise.
203
217
  """
204
218
  if not self.proxy_list:
205
- return True
219
+ return True # No specific proxies to trust, so always valid.
206
220
 
207
221
  ip_count = len(ip_list)
208
222
  proxy_list_count = len(self.proxy_list)
209
223
 
210
- # in strict mode, total ip count must be 1 more than proxy count
211
224
  if strict and ip_count - 1 != proxy_list_count:
212
- return False
225
+ return False # Strict mode: Exact count match required.
213
226
 
214
- # total ip count (client + proxies) must be more than proxy count
215
227
  if ip_count - 1 < proxy_list_count:
216
- return False
217
-
218
- # start from the end, slice the incoming ip list to the same length as the trusted proxy list
219
- ip_list_slice = ip_list[-proxy_list_count:]
220
- for index, value in enumerate(ip_list_slice):
221
- if not str(value).startswith(self.proxy_list[index]):
222
- return False
228
+ return False # Not enough IPs to match the trusted proxies.
223
229
 
224
- # now all we need is to return the first ip in the list that is not in the trusted proxy list
225
- # best_client_ip_index = proxy_list_count + 1
226
- # best_client_ip = ip_list[-best_client_ip_index]
227
-
228
- return True
230
+ # Verify each proxy against the trusted list by comparing each corresponding element.
231
+ return all(
232
+ str(ip).startswith(trusted_proxy_pattern)
233
+ for ip, trusted_proxy_pattern in zip(
234
+ ip_list[-proxy_list_count:], self.proxy_list
235
+ )
236
+ )
229
237
 
230
238
 
231
239
  class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
232
240
  """
233
- A class that makes best effort to determine the client's IP address.
241
+ A class that makes a best effort to determine the client's IP address.
234
242
  """
235
243
 
236
244
  def __init__(
237
245
  self,
238
246
  precedence: Optional[Tuple[str, ...]] = None,
239
247
  leftmost: bool = True,
240
- proxy_count: int = 0,
248
+ proxy_count: Optional[int] = None,
241
249
  proxy_list: Optional[List[str]] = None,
242
250
  ) -> None:
243
251
  IpWareMeta.__init__(self, precedence, leftmost)
244
- IpWareProxy.__init__(self, proxy_count or 0, proxy_list or [])
252
+ IpWareProxy.__init__(self, proxy_count, proxy_list)
245
253
 
246
254
  def get_meta_value(self, meta: Dict[str, str], key: str) -> str:
247
255
  """
248
- Given a key, it returns a cleaned up version of the value
249
- @param key: the key to lookup
250
- @return: the value of the key or empty string
256
+ Returns a cleaned up version of the value for a given key.
257
+ @param key: The key to look up.
258
+ @return: The value of the key or an empty string if the key is not found.
251
259
  """
252
260
  meta = meta or {}
253
261
  return meta.get(key, meta.get(key.replace("_", "-"), "")).strip()
254
262
 
255
263
  def get_meta_values(self, meta: Dict[str, str]) -> List[str]:
256
264
  """
257
- Given a list of keys, it returns a list of cleaned up values
258
- @return: a list of values
265
+ Returns a list of cleaned up values for the keys defined in 'precedence'.
266
+ @return: A list of values.
259
267
  """
260
- return [self.get_meta_value(meta, key) for key in self.precedence]
268
+ meta_list: List[str] = []
269
+ for key in self.precedence:
270
+ value = self.get_meta_value(meta, key).strip()
271
+ if value:
272
+ meta_list.append(value)
273
+ return meta_list
261
274
 
262
275
  def get_client_ip(
263
276
  self,
@@ -267,15 +280,11 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
267
280
  """
268
281
  Returns the client's IP address.
269
282
  """
270
-
271
283
  loopback_list: List[IpAddressType] = []
272
284
  private_list: List[OptionalIpAddressType] = []
273
285
 
274
286
  for ip_str in self.get_meta_values(meta):
275
- if not ip_str:
276
- continue
277
-
278
- ip_list = self.get_ips_from_string(ip_str)
287
+ ip_list = self.get_ips_from_string(ip_str, strict)
279
288
  if not ip_list:
280
289
  continue
281
290
 
@@ -287,60 +296,40 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
287
296
  if not proxy_list_validated:
288
297
  continue
289
298
 
290
- client_ip, trusted_route = self.get_best_ip(
291
- ip_list, proxy_count_validated, proxy_list_validated
292
- )
293
-
294
- # we found a global ip, return it
295
- if client_ip is not None and client_ip.is_global:
296
- return client_ip, trusted_route
297
-
298
- # we found a private ip, save it
299
- if client_ip is not None and client_ip.is_loopback:
300
- loopback_list.append(client_ip)
301
- else:
302
- # if not global (public) or loopback (local), we treat it asd private
303
- private_list.append(client_ip)
299
+ client_ip, trusted_route = self.get_best_ip(ip_list)
300
+ if client_ip is not None:
301
+ if client_ip.is_global:
302
+ return client_ip, trusted_route
303
+ if client_ip.is_loopback:
304
+ loopback_list.append(client_ip)
305
+ else:
306
+ private_list.append(client_ip)
304
307
 
305
- # we have not been able to locate a global ip
306
- # it could be the server is running on the intranet
307
- # we will return the first private ip we found
308
308
  if private_list:
309
309
  return private_list[0], False
310
-
311
- # we have not been able to locate a global ip, nor a private ip
312
- # it could be the server is running on a loopback address serving local requests
313
310
  if loopback_list:
314
311
  return loopback_list[0], False
315
-
316
- # we were unable to find any ip address
317
312
  return None, False
318
313
 
319
314
  def get_best_ip(
320
315
  self,
321
316
  ip_list: List[IpAddressType],
322
- proxy_count_validated: bool = True,
323
- proxy_list_validated: bool = True,
324
317
  ) -> Tuple[OptionalIpAddressType, bool]:
325
318
  """
326
- Returns the best possible ip for the client.
319
+ Determines the best possible IP address for the client from a list of IP addresses.
327
320
  """
328
-
329
321
  if not ip_list:
330
- logger.warning("Invalid ip list provided.")
322
+ logger.warning("Invalid IP list provided.")
331
323
  return None, False
332
324
 
333
- # the incoming ips match our trusted proxy list
334
- if len(self.proxy_list) > 0 and proxy_list_validated:
325
+ if self.proxy_list and len(self.proxy_list) > 0:
335
326
  best_client_ip_index = len(self.proxy_list) + 1
336
327
  best_client_ip = ip_list[-best_client_ip_index]
337
328
  return best_client_ip, True
338
329
 
339
- # the incoming ips match our proxy count
340
- if self.proxy_count > 0 and proxy_count_validated:
330
+ if self.proxy_count is not None:
341
331
  best_client_ip_index = self.proxy_count + 1
342
332
  best_client_ip = ip_list[-best_client_ip_index]
343
333
  return best_client_ip, True
344
334
 
345
- # we don't track proxy related info, so we just return the first ip
346
335
  return ip_list[0], False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-ipware
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: A Python package to retrieve user's IP address
5
5
  Author-email: Val Neekman <info@neekware.com>
6
6
  License: MIT
@@ -216,23 +216,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
216
216
  You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
217
217
 
218
218
  ```python
219
- # In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
220
219
  from python_ipware import IpWare
221
220
 
222
- # enforce proxy count
223
- ipw = IpWare(proxy_count=1)
221
+ # Enforce proxy count
222
+ # proxy_count=0 is valid
223
+ # proxy_count=None to disable proxy_count check
224
+ ipw = IpWare(proxy_count=2)
224
225
 
225
- # enforce proxy count and trusted proxies
226
- ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
226
+ # Example usage in non-strict mode:
227
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
228
+ # At least `proxy_count` number of proxies
229
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
227
230
 
231
+ # Example usage in strict mode:
232
+ # X-Forwarded-For format: <client>, <proxy1>, <proxy2>
233
+ # Exact `proxy_count` number of proxies
234
+ ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
235
+ ```
228
236
 
229
- # usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
230
- # total number of ip addresses are greater than the total count
231
- ip, trusted_route = ipw.get_client_ip(meta=request.META)
237
+ ### Proxy Count & Trusted Proxy List Combo
238
+ In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
232
239
 
240
+ ```python
241
+ from python_ipware import IpWare
233
242
 
234
- # usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
235
- # total number of ip addresses are exactly equal to client ip + proxy_count
243
+ # Enforce both proxy count and trusted proxies
244
+ ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
245
+
246
+ # Example usage in non-strict mode:
247
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
248
+ # At least `proxy_count` number of proxies
249
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
250
+
251
+ # Example usage in strict mode:
252
+ # X-Forwarded-For format: <client>, <proxy1>
253
+ # Exact `proxy_count` number of proxies
236
254
  ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
237
255
  ```
238
256
 
@@ -0,0 +1,9 @@
1
+ python_ipware/__init__.py,sha256=TCZx4mt5Gxr2D_zNU6j0ynopdsAN_v4xeiTWz1OXSpY,87
2
+ python_ipware/__version__.py,sha256=xEb7Z4b8xalXXExBg42XPAhbJKniHzcsEPjp-6S3ppg,22
3
+ python_ipware/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ python_ipware/python_ipware.py,sha256=IotMZQIGSOu2XCbmdYH4WuhBvXm-lJVJHe3FXylSMvc,12725
5
+ python_ipware-2.0.5.dist-info/LICENSE,sha256=MLpNxpqfTc4TLdcDk3x6k7Vz4lJGBNLV-SxQZlFMDU8,1103
6
+ python_ipware-2.0.5.dist-info/METADATA,sha256=3Lsxn8HubcN0KnuwggZLcDu5h67rZc5YOn5ad-YNNdY,15420
7
+ python_ipware-2.0.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
8
+ python_ipware-2.0.5.dist-info/top_level.txt,sha256=URE4dvjpReJtRHyQpJqoBow5EWt_RqVXFn85B9Tq5Xw,14
9
+ python_ipware-2.0.5.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- python_ipware/__init__.py,sha256=TCZx4mt5Gxr2D_zNU6j0ynopdsAN_v4xeiTWz1OXSpY,87
2
- python_ipware/__version__.py,sha256=_GEKEa6BYjBV34SZkSlAR87aCM5Y9G0aSI0LXL52iJg,22
3
- python_ipware/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- python_ipware/python_ipware.py,sha256=kAoCyLbJLxN-zocHB9Ps-AdZYWMUQ8KasHscg6Qcc3Q,12119
5
- python_ipware-2.0.3.dist-info/LICENSE,sha256=MLpNxpqfTc4TLdcDk3x6k7Vz4lJGBNLV-SxQZlFMDU8,1103
6
- python_ipware-2.0.3.dist-info/METADATA,sha256=cwAXk8j8o_cfJUfZT9tQ3sCwjRa6OQRVVX89juDwJgA,14857
7
- python_ipware-2.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
8
- python_ipware-2.0.3.dist-info/top_level.txt,sha256=URE4dvjpReJtRHyQpJqoBow5EWt_RqVXFn85B9Tq5Xw,14
9
- python_ipware-2.0.3.dist-info/RECORD,,