Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/gunicorn/util.py: 31%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# This file is part of gunicorn released under the MIT license.
3# See the NOTICE for more information.
4import ast
5import email.utils
6import errno
7import fcntl
8import html
9import importlib
10import inspect
11import io
12import logging
13import os
14import pwd
15import random
16import re
17import socket
18import sys
19import textwrap
20import time
21import traceback
22import warnings
24try:
25 import importlib.metadata as importlib_metadata
26except (ModuleNotFoundError, ImportError):
27 import importlib_metadata
29from gunicorn.errors import AppImportError
30from gunicorn.workers import SUPPORTED_WORKERS
31import urllib.parse
33REDIRECT_TO = getattr(os, 'devnull', '/dev/null')
35# Server and Date aren't technically hop-by-hop
36# headers, but they are in the purview of the
37# origin server which the WSGI spec says we should
38# act like. So we drop them and add our own.
39#
40# In the future, concatenation server header values
41# might be better, but nothing else does it and
42# dropping them is easier.
43hop_headers = set("""
44 connection keep-alive proxy-authenticate proxy-authorization
45 te trailers transfer-encoding upgrade
46 server date
47 """.split())
49# setproctitle causes segfaults on macOS due to fork() safety issues
50# https://github.com/benoitc/gunicorn/issues/3021
51if sys.platform == "darwin":
52 def _setproctitle(title):
53 pass
54else:
55 try:
56 from setproctitle import setproctitle, getproctitle
58 # Force early initialization before any os.environ modifications
59 # (e.g. removing LISTEN_FDS in systemd socket activation)
60 # https://github.com/benoitc/gunicorn/issues/3430
61 getproctitle()
63 def _setproctitle(title):
64 setproctitle("gunicorn: %s" % title)
65 except ImportError:
66 def _setproctitle(title):
67 pass
70def load_entry_point(distribution, group, name):
71 dist_obj = importlib_metadata.distribution(distribution)
72 eps = [ep for ep in dist_obj.entry_points
73 if ep.group == group and ep.name == name]
74 if not eps:
75 raise ImportError("Entry point %r not found" % ((group, name),))
76 return eps[0].load()
79def load_class(uri, default="gunicorn.workers.sync.SyncWorker",
80 section="gunicorn.workers"):
81 if inspect.isclass(uri):
82 return uri
83 if uri.startswith("egg:"):
84 # uses entry points
85 entry_str = uri.split("egg:")[1]
86 try:
87 dist, name = entry_str.rsplit("#", 1)
88 except ValueError:
89 dist = entry_str
90 name = default
92 try:
93 return load_entry_point(dist, section, name)
94 except Exception:
95 exc = traceback.format_exc()
96 msg = "class uri %r invalid or not found: \n\n[%s]"
97 raise RuntimeError(msg % (uri, exc))
98 else:
99 components = uri.split('.')
100 if len(components) == 1:
101 while True:
102 if uri.startswith("#"):
103 uri = uri[1:]
105 if uri in SUPPORTED_WORKERS:
106 components = SUPPORTED_WORKERS[uri].split(".")
107 break
109 try:
110 return load_entry_point(
111 "gunicorn", section, uri
112 )
113 except Exception:
114 exc = traceback.format_exc()
115 msg = "class uri %r invalid or not found: \n\n[%s]"
116 raise RuntimeError(msg % (uri, exc))
118 klass = components.pop(-1)
120 try:
121 mod = importlib.import_module('.'.join(components))
122 except Exception:
123 exc = traceback.format_exc()
124 msg = "class uri %r invalid or not found: \n\n[%s]"
125 raise RuntimeError(msg % (uri, exc))
126 return getattr(mod, klass)
129positionals = (
130 inspect.Parameter.POSITIONAL_ONLY,
131 inspect.Parameter.POSITIONAL_OR_KEYWORD,
132)
135def get_arity(f):
136 sig = inspect.signature(f)
137 arity = 0
139 for param in sig.parameters.values():
140 if param.kind in positionals:
141 arity += 1
143 return arity
146def get_username(uid):
147 """ get the username for a user id"""
148 return pwd.getpwuid(uid).pw_name
151def set_owner_process(uid, gid, initgroups=False):
152 """ set user and group of workers processes """
154 if gid:
155 if uid:
156 try:
157 username = get_username(uid)
158 except KeyError:
159 initgroups = False
161 if initgroups:
162 os.initgroups(username, gid)
163 elif gid != os.getgid():
164 os.setgid(gid)
166 if uid and uid != os.getuid():
167 os.setuid(uid)
170def chown(path, uid, gid):
171 os.chown(path, uid, gid)
174if sys.platform.startswith("win"):
175 def _waitfor(func, pathname, waitall=False):
176 # Perform the operation
177 func(pathname)
178 # Now setup the wait loop
179 if waitall:
180 dirname = pathname
181 else:
182 dirname, name = os.path.split(pathname)
183 dirname = dirname or '.'
184 # Check for `pathname` to be removed from the filesystem.
185 # The exponential backoff of the timeout amounts to a total
186 # of ~1 second after which the deletion is probably an error
187 # anyway.
188 # Testing on a i7@4.3GHz shows that usually only 1 iteration is
189 # required when contention occurs.
190 timeout = 0.001
191 while timeout < 1.0:
192 # Note we are only testing for the existence of the file(s) in
193 # the contents of the directory regardless of any security or
194 # access rights. If we have made it this far, we have sufficient
195 # permissions to do that much using Python's equivalent of the
196 # Windows API FindFirstFile.
197 # Other Windows APIs can fail or give incorrect results when
198 # dealing with files that are pending deletion.
199 L = os.listdir(dirname)
200 if not L if waitall else name in L:
201 return
202 # Increase the timeout and try again
203 time.sleep(timeout)
204 timeout *= 2
205 warnings.warn('tests may fail, delete still pending for ' + pathname,
206 RuntimeWarning, stacklevel=4)
208 def _unlink(filename):
209 _waitfor(os.unlink, filename)
210else:
211 _unlink = os.unlink
214def unlink(filename):
215 try:
216 _unlink(filename)
217 except OSError as error:
218 # The filename need not exist.
219 if error.errno not in (errno.ENOENT, errno.ENOTDIR):
220 raise
223def is_ipv6(addr):
224 try:
225 socket.inet_pton(socket.AF_INET6, addr)
226 except OSError: # not a valid address
227 return False
228 except ValueError: # ipv6 not supported on this platform
229 return False
230 return True
233def parse_address(netloc, default_port='8000'):
234 if re.match(r'unix:(//)?', netloc):
235 return re.split(r'unix:(//)?', netloc)[-1]
237 if netloc.startswith("fd://"):
238 fd = netloc[5:]
239 try:
240 return int(fd)
241 except ValueError:
242 raise RuntimeError("%r is not a valid file descriptor." % fd) from None
244 if netloc.startswith("tcp://"):
245 netloc = netloc.split("tcp://")[1]
246 host, port = netloc, default_port
248 if '[' in netloc and ']' in netloc:
249 host = netloc.split(']')[0][1:]
250 port = (netloc.split(']:') + [default_port])[1]
251 elif ':' in netloc:
252 host, port = (netloc.split(':') + [default_port])[:2]
253 elif netloc == "":
254 host, port = "0.0.0.0", default_port
256 try:
257 port = int(port)
258 except ValueError:
259 raise RuntimeError("%r is not a valid port number." % port)
261 return host.lower(), port
264def close_on_exec(fd):
265 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
266 flags |= fcntl.FD_CLOEXEC
267 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
270def set_non_blocking(fd):
271 flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
272 fcntl.fcntl(fd, fcntl.F_SETFL, flags)
275def close(sock):
276 try:
277 sock.close()
278 except OSError:
279 pass
282try:
283 from os import closerange
284except ImportError:
285 def closerange(fd_low, fd_high):
286 # Iterate through and close all file descriptors.
287 for fd in range(fd_low, fd_high):
288 try:
289 os.close(fd)
290 except OSError: # ERROR, fd wasn't open to begin with (ignored)
291 pass
294def write_chunk(sock, data):
295 if isinstance(data, str):
296 data = data.encode('utf-8')
297 chunk_size = "%X\r\n" % len(data)
298 chunk = b"".join([chunk_size.encode('utf-8'), data, b"\r\n"])
299 sock.sendall(chunk)
302def write(sock, data, chunked=False):
303 if chunked:
304 return write_chunk(sock, data)
305 sock.sendall(data)
308def write_nonblock(sock, data, chunked=False):
309 timeout = sock.gettimeout()
310 if timeout != 0.0:
311 try:
312 sock.setblocking(0)
313 return write(sock, data, chunked)
314 finally:
315 sock.setblocking(1)
316 else:
317 return write(sock, data, chunked)
320def write_error(sock, status_int, reason, mesg):
321 html_error = textwrap.dedent("""\
322 <html>
323 <head>
324 <title>%(reason)s</title>
325 </head>
326 <body>
327 <h1><p>%(reason)s</p></h1>
328 %(mesg)s
329 </body>
330 </html>
331 """) % {"reason": reason, "mesg": html.escape(mesg)}
333 http = textwrap.dedent("""\
334 HTTP/1.1 %s %s\r
335 Connection: close\r
336 Content-Type: text/html\r
337 Content-Length: %d\r
338 \r
339 %s""") % (str(status_int), reason, len(html_error), html_error)
340 write_nonblock(sock, http.encode('latin1'))
343def _called_with_wrong_args(f):
344 """Check whether calling a function raised a ``TypeError`` because
345 the call failed or because something in the function raised the
346 error.
348 :param f: The function that was called.
349 :return: ``True`` if the call failed.
350 """
351 tb = sys.exc_info()[2]
353 try:
354 while tb is not None:
355 if tb.tb_frame.f_code is f.__code__:
356 # In the function, it was called successfully.
357 return False
359 tb = tb.tb_next
361 # Didn't reach the function.
362 return True
363 finally:
364 # Delete tb to break a circular reference in Python 2.
365 # https://docs.python.org/2/library/sys.html#sys.exc_info
366 del tb
369def import_app(module):
370 parts = module.split(":", 1)
371 if len(parts) == 1:
372 obj = "application"
373 else:
374 module, obj = parts[0], parts[1]
376 try:
377 mod = importlib.import_module(module)
378 except ImportError:
379 if module.endswith(".py") and os.path.exists(module):
380 msg = "Failed to find application, did you mean '%s:%s'?"
381 raise ImportError(msg % (module.rsplit(".", 1)[0], obj))
382 raise
384 # Parse obj as a single expression to determine if it's a valid
385 # attribute name or function call.
386 try:
387 expression = ast.parse(obj, mode="eval").body
388 except SyntaxError:
389 raise AppImportError(
390 "Failed to parse %r as an attribute name or function call." % obj
391 )
393 if isinstance(expression, ast.Name):
394 name = expression.id
395 args = kwargs = None
396 elif isinstance(expression, ast.Call):
397 # Ensure the function name is an attribute name only.
398 if not isinstance(expression.func, ast.Name):
399 raise AppImportError("Function reference must be a simple name: %r" % obj)
401 name = expression.func.id
403 # Parse the positional and keyword arguments as literals.
404 try:
405 args = [ast.literal_eval(arg) for arg in expression.args]
406 kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expression.keywords}
407 except ValueError:
408 # literal_eval gives cryptic error messages, show a generic
409 # message with the full expression instead.
410 raise AppImportError(
411 "Failed to parse arguments as literal values: %r" % obj
412 )
413 else:
414 raise AppImportError(
415 "Failed to parse %r as an attribute name or function call." % obj
416 )
418 is_debug = logging.root.level == logging.DEBUG
419 try:
420 app = getattr(mod, name)
421 except AttributeError:
422 if is_debug:
423 traceback.print_exception(*sys.exc_info())
424 raise AppImportError("Failed to find attribute %r in %r." % (name, module))
426 # If the expression was a function call, call the retrieved object
427 # to get the real application.
428 if args is not None:
429 try:
430 app = app(*args, **kwargs)
431 except TypeError as e:
432 # If the TypeError was due to bad arguments to the factory
433 # function, show Python's nice error message without a
434 # traceback.
435 if _called_with_wrong_args(app):
436 raise AppImportError(
437 "".join(traceback.format_exception_only(TypeError, e)).strip()
438 )
440 # Otherwise it was raised from within the function, show the
441 # full traceback.
442 raise
444 if app is None:
445 raise AppImportError("Failed to find application object: %r" % obj)
447 if not callable(app):
448 raise AppImportError("Application object must be callable.")
449 return app
452def getcwd():
453 # get current path, try to use PWD env first
454 try:
455 a = os.stat(os.environ['PWD'])
456 b = os.stat(os.getcwd())
457 if a.st_ino == b.st_ino and a.st_dev == b.st_dev:
458 cwd = os.environ['PWD']
459 else:
460 cwd = os.getcwd()
461 except Exception:
462 cwd = os.getcwd()
463 return cwd
466def http_date(timestamp=None):
467 """Return the current date and time formatted for a message header."""
468 if timestamp is None:
469 timestamp = time.time()
470 s = email.utils.formatdate(timestamp, localtime=False, usegmt=True)
471 return s
474def is_hoppish(header):
475 return header.lower().strip() in hop_headers
478def daemonize(enable_stdio_inheritance=False):
479 """\
480 Standard daemonization of a process.
481 http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7
482 """
483 if 'GUNICORN_FD' not in os.environ:
484 if os.fork():
485 os._exit(0)
486 os.setsid()
488 if os.fork():
489 os._exit(0)
491 os.umask(0o22)
493 # In both the following any file descriptors above stdin
494 # stdout and stderr are left untouched. The inheritance
495 # option simply allows one to have output go to a file
496 # specified by way of shell redirection when not wanting
497 # to use --error-log option.
499 if not enable_stdio_inheritance:
500 # Remap all of stdin, stdout and stderr on to
501 # /dev/null. The expectation is that users have
502 # specified the --error-log option.
504 closerange(0, 3)
506 fd_null = os.open(REDIRECT_TO, os.O_RDWR)
507 # PEP 446, make fd for /dev/null inheritable
508 os.set_inheritable(fd_null, True)
510 # expect fd_null to be always 0 here, but in-case not ...
511 if fd_null != 0:
512 os.dup2(fd_null, 0)
514 os.dup2(fd_null, 1)
515 os.dup2(fd_null, 2)
517 else:
518 fd_null = os.open(REDIRECT_TO, os.O_RDWR)
520 # Always redirect stdin to /dev/null as we would
521 # never expect to need to read interactive input.
523 if fd_null != 0:
524 os.close(0)
525 os.dup2(fd_null, 0)
527 # If stdout and stderr are still connected to
528 # their original file descriptors we check to see
529 # if they are associated with terminal devices.
530 # When they are we map them to /dev/null so that
531 # are still detached from any controlling terminal
532 # properly. If not we preserve them as they are.
533 #
534 # If stdin and stdout were not hooked up to the
535 # original file descriptors, then all bets are
536 # off and all we can really do is leave them as
537 # they were.
538 #
539 # This will allow 'gunicorn ... > output.log 2>&1'
540 # to work with stdout/stderr going to the file
541 # as expected.
542 #
543 # Note that if using --error-log option, the log
544 # file specified through shell redirection will
545 # only be used up until the log file specified
546 # by the option takes over. As it replaces stdout
547 # and stderr at the file descriptor level, then
548 # anything using stdout or stderr, including having
549 # cached a reference to them, will still work.
551 def redirect(stream, fd_expect):
552 try:
553 fd = stream.fileno()
554 if fd == fd_expect and stream.isatty():
555 os.close(fd)
556 os.dup2(fd_null, fd)
557 except AttributeError:
558 pass
560 redirect(sys.stdout, 1)
561 redirect(sys.stderr, 2)
564def seed():
565 try:
566 random.seed(os.urandom(64))
567 except NotImplementedError:
568 random.seed('%s.%s' % (time.time(), os.getpid()))
571def check_is_writable(path):
572 try:
573 with open(path, 'a') as f:
574 f.close()
575 except OSError as e:
576 raise RuntimeError("Error: '%s' isn't writable [%r]" % (path, e))
579def to_bytestring(value, encoding="utf8"):
580 """Converts a string argument to a byte string"""
581 if isinstance(value, bytes):
582 return value
583 if not isinstance(value, str):
584 raise TypeError('%r is not a string' % value)
586 return value.encode(encoding)
589def has_fileno(obj):
590 if not hasattr(obj, "fileno"):
591 return False
593 # check BytesIO case and maybe others
594 try:
595 obj.fileno()
596 except (AttributeError, OSError, io.UnsupportedOperation):
597 return False
599 return True
602def warn(msg):
603 print("!!!", file=sys.stderr)
605 lines = msg.splitlines()
606 for i, line in enumerate(lines):
607 if i == 0:
608 line = "WARNING: %s" % line
609 print("!!! %s" % line, file=sys.stderr)
611 print("!!!\n", file=sys.stderr)
612 sys.stderr.flush()
615def make_fail_app(msg):
616 msg = to_bytestring(msg)
618 def app(environ, start_response):
619 start_response("500 Internal Server Error", [
620 ("Content-Type", "text/plain"),
621 ("Content-Length", str(len(msg)))
622 ])
623 return [msg]
625 return app
628def split_request_uri(uri):
629 if uri.startswith("//"):
630 # When the path starts with //, urlsplit considers it as a
631 # relative uri while the RFC says we should consider it as abs_path
632 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
633 # We use temporary dot prefix to workaround this behaviour
634 parts = urllib.parse.urlsplit("." + uri)
635 return parts._replace(path=parts.path[1:])
637 return urllib.parse.urlsplit(uri)
640# From six.reraise
641def reraise(tp, value, tb=None):
642 try:
643 if value is None:
644 value = tp()
645 if value.__traceback__ is not tb:
646 raise value.with_traceback(tb)
647 raise value
648 finally:
649 value = None
650 tb = None
653def bytes_to_str(b):
654 if isinstance(b, str):
655 return b
656 return str(b, 'latin1')
659def unquote_to_wsgi_str(string):
660 return urllib.parse.unquote_to_bytes(string).decode('latin-1')