Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/gunicorn/util.py: 39%
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())
49try:
50 from setproctitle import setproctitle
52 def _setproctitle(title):
53 setproctitle("gunicorn: %s" % title)
54except ImportError:
55 def _setproctitle(title):
56 pass
59def load_entry_point(distribution, group, name):
60 dist_obj = importlib_metadata.distribution(distribution)
61 eps = [ep for ep in dist_obj.entry_points
62 if ep.group == group and ep.name == name]
63 if not eps:
64 raise ImportError("Entry point %r not found" % ((group, name),))
65 return eps[0].load()
68def load_class(uri, default="gunicorn.workers.sync.SyncWorker",
69 section="gunicorn.workers"):
70 if inspect.isclass(uri):
71 return uri
72 if uri.startswith("egg:"):
73 # uses entry points
74 entry_str = uri.split("egg:")[1]
75 try:
76 dist, name = entry_str.rsplit("#", 1)
77 except ValueError:
78 dist = entry_str
79 name = default
81 try:
82 return load_entry_point(dist, section, name)
83 except Exception:
84 exc = traceback.format_exc()
85 msg = "class uri %r invalid or not found: \n\n[%s]"
86 raise RuntimeError(msg % (uri, exc))
87 else:
88 components = uri.split('.')
89 if len(components) == 1:
90 while True:
91 if uri.startswith("#"):
92 uri = uri[1:]
94 if uri in SUPPORTED_WORKERS:
95 components = SUPPORTED_WORKERS[uri].split(".")
96 break
98 try:
99 return load_entry_point(
100 "gunicorn", section, uri
101 )
102 except Exception:
103 exc = traceback.format_exc()
104 msg = "class uri %r invalid or not found: \n\n[%s]"
105 raise RuntimeError(msg % (uri, exc))
107 klass = components.pop(-1)
109 try:
110 mod = importlib.import_module('.'.join(components))
111 except Exception:
112 exc = traceback.format_exc()
113 msg = "class uri %r invalid or not found: \n\n[%s]"
114 raise RuntimeError(msg % (uri, exc))
115 return getattr(mod, klass)
118positionals = (
119 inspect.Parameter.POSITIONAL_ONLY,
120 inspect.Parameter.POSITIONAL_OR_KEYWORD,
121)
124def get_arity(f):
125 sig = inspect.signature(f)
126 arity = 0
128 for param in sig.parameters.values():
129 if param.kind in positionals:
130 arity += 1
132 return arity
135def get_username(uid):
136 """ get the username for a user id"""
137 return pwd.getpwuid(uid).pw_name
140def set_owner_process(uid, gid, initgroups=False):
141 """ set user and group of workers processes """
143 if gid:
144 if uid:
145 try:
146 username = get_username(uid)
147 except KeyError:
148 initgroups = False
150 if initgroups:
151 os.initgroups(username, gid)
152 elif gid != os.getgid():
153 os.setgid(gid)
155 if uid and uid != os.getuid():
156 os.setuid(uid)
159def chown(path, uid, gid):
160 os.chown(path, uid, gid)
163if sys.platform.startswith("win"):
164 def _waitfor(func, pathname, waitall=False):
165 # Perform the operation
166 func(pathname)
167 # Now setup the wait loop
168 if waitall:
169 dirname = pathname
170 else:
171 dirname, name = os.path.split(pathname)
172 dirname = dirname or '.'
173 # Check for `pathname` to be removed from the filesystem.
174 # The exponential backoff of the timeout amounts to a total
175 # of ~1 second after which the deletion is probably an error
176 # anyway.
177 # Testing on a i7@4.3GHz shows that usually only 1 iteration is
178 # required when contention occurs.
179 timeout = 0.001
180 while timeout < 1.0:
181 # Note we are only testing for the existence of the file(s) in
182 # the contents of the directory regardless of any security or
183 # access rights. If we have made it this far, we have sufficient
184 # permissions to do that much using Python's equivalent of the
185 # Windows API FindFirstFile.
186 # Other Windows APIs can fail or give incorrect results when
187 # dealing with files that are pending deletion.
188 L = os.listdir(dirname)
189 if not L if waitall else name in L:
190 return
191 # Increase the timeout and try again
192 time.sleep(timeout)
193 timeout *= 2
194 warnings.warn('tests may fail, delete still pending for ' + pathname,
195 RuntimeWarning, stacklevel=4)
197 def _unlink(filename):
198 _waitfor(os.unlink, filename)
199else:
200 _unlink = os.unlink
203def unlink(filename):
204 try:
205 _unlink(filename)
206 except OSError as error:
207 # The filename need not exist.
208 if error.errno not in (errno.ENOENT, errno.ENOTDIR):
209 raise
212def is_ipv6(addr):
213 try:
214 socket.inet_pton(socket.AF_INET6, addr)
215 except OSError: # not a valid address
216 return False
217 except ValueError: # ipv6 not supported on this platform
218 return False
219 return True
222def parse_address(netloc, default_port='8000'):
223 if re.match(r'unix:(//)?', netloc):
224 return re.split(r'unix:(//)?', netloc)[-1]
226 if netloc.startswith("fd://"):
227 fd = netloc[5:]
228 try:
229 return int(fd)
230 except ValueError:
231 raise RuntimeError("%r is not a valid file descriptor." % fd) from None
233 if netloc.startswith("tcp://"):
234 netloc = netloc.split("tcp://")[1]
235 host, port = netloc, default_port
237 if '[' in netloc and ']' in netloc:
238 host = netloc.split(']')[0][1:]
239 port = (netloc.split(']:') + [default_port])[1]
240 elif ':' in netloc:
241 host, port = (netloc.split(':') + [default_port])[:2]
242 elif netloc == "":
243 host, port = "0.0.0.0", default_port
245 try:
246 port = int(port)
247 except ValueError:
248 raise RuntimeError("%r is not a valid port number." % port)
250 return host.lower(), port
253def close_on_exec(fd):
254 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
255 flags |= fcntl.FD_CLOEXEC
256 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
259def set_non_blocking(fd):
260 flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
261 fcntl.fcntl(fd, fcntl.F_SETFL, flags)
264def close(sock):
265 try:
266 sock.close()
267 except OSError:
268 pass
271try:
272 from os import closerange
273except ImportError:
274 def closerange(fd_low, fd_high):
275 # Iterate through and close all file descriptors.
276 for fd in range(fd_low, fd_high):
277 try:
278 os.close(fd)
279 except OSError: # ERROR, fd wasn't open to begin with (ignored)
280 pass
283def write_chunk(sock, data):
284 if isinstance(data, str):
285 data = data.encode('utf-8')
286 chunk_size = "%X\r\n" % len(data)
287 chunk = b"".join([chunk_size.encode('utf-8'), data, b"\r\n"])
288 sock.sendall(chunk)
291def write(sock, data, chunked=False):
292 if chunked:
293 return write_chunk(sock, data)
294 sock.sendall(data)
297def write_nonblock(sock, data, chunked=False):
298 timeout = sock.gettimeout()
299 if timeout != 0.0:
300 try:
301 sock.setblocking(0)
302 return write(sock, data, chunked)
303 finally:
304 sock.setblocking(1)
305 else:
306 return write(sock, data, chunked)
309def write_error(sock, status_int, reason, mesg):
310 html_error = textwrap.dedent("""\
311 <html>
312 <head>
313 <title>%(reason)s</title>
314 </head>
315 <body>
316 <h1><p>%(reason)s</p></h1>
317 %(mesg)s
318 </body>
319 </html>
320 """) % {"reason": reason, "mesg": html.escape(mesg)}
322 http = textwrap.dedent("""\
323 HTTP/1.1 %s %s\r
324 Connection: close\r
325 Content-Type: text/html\r
326 Content-Length: %d\r
327 \r
328 %s""") % (str(status_int), reason, len(html_error), html_error)
329 write_nonblock(sock, http.encode('latin1'))
332def _called_with_wrong_args(f):
333 """Check whether calling a function raised a ``TypeError`` because
334 the call failed or because something in the function raised the
335 error.
337 :param f: The function that was called.
338 :return: ``True`` if the call failed.
339 """
340 tb = sys.exc_info()[2]
342 try:
343 while tb is not None:
344 if tb.tb_frame.f_code is f.__code__:
345 # In the function, it was called successfully.
346 return False
348 tb = tb.tb_next
350 # Didn't reach the function.
351 return True
352 finally:
353 # Delete tb to break a circular reference in Python 2.
354 # https://docs.python.org/2/library/sys.html#sys.exc_info
355 del tb
358def import_app(module):
359 parts = module.split(":", 1)
360 if len(parts) == 1:
361 obj = "application"
362 else:
363 module, obj = parts[0], parts[1]
365 try:
366 mod = importlib.import_module(module)
367 except ImportError:
368 if module.endswith(".py") and os.path.exists(module):
369 msg = "Failed to find application, did you mean '%s:%s'?"
370 raise ImportError(msg % (module.rsplit(".", 1)[0], obj))
371 raise
373 # Parse obj as a single expression to determine if it's a valid
374 # attribute name or function call.
375 try:
376 expression = ast.parse(obj, mode="eval").body
377 except SyntaxError:
378 raise AppImportError(
379 "Failed to parse %r as an attribute name or function call." % obj
380 )
382 if isinstance(expression, ast.Name):
383 name = expression.id
384 args = kwargs = None
385 elif isinstance(expression, ast.Call):
386 # Ensure the function name is an attribute name only.
387 if not isinstance(expression.func, ast.Name):
388 raise AppImportError("Function reference must be a simple name: %r" % obj)
390 name = expression.func.id
392 # Parse the positional and keyword arguments as literals.
393 try:
394 args = [ast.literal_eval(arg) for arg in expression.args]
395 kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expression.keywords}
396 except ValueError:
397 # literal_eval gives cryptic error messages, show a generic
398 # message with the full expression instead.
399 raise AppImportError(
400 "Failed to parse arguments as literal values: %r" % obj
401 )
402 else:
403 raise AppImportError(
404 "Failed to parse %r as an attribute name or function call." % obj
405 )
407 is_debug = logging.root.level == logging.DEBUG
408 try:
409 app = getattr(mod, name)
410 except AttributeError:
411 if is_debug:
412 traceback.print_exception(*sys.exc_info())
413 raise AppImportError("Failed to find attribute %r in %r." % (name, module))
415 # If the expression was a function call, call the retrieved object
416 # to get the real application.
417 if args is not None:
418 try:
419 app = app(*args, **kwargs)
420 except TypeError as e:
421 # If the TypeError was due to bad arguments to the factory
422 # function, show Python's nice error message without a
423 # traceback.
424 if _called_with_wrong_args(app):
425 raise AppImportError(
426 "".join(traceback.format_exception_only(TypeError, e)).strip()
427 )
429 # Otherwise it was raised from within the function, show the
430 # full traceback.
431 raise
433 if app is None:
434 raise AppImportError("Failed to find application object: %r" % obj)
436 if not callable(app):
437 raise AppImportError("Application object must be callable.")
438 return app
441def getcwd():
442 # get current path, try to use PWD env first
443 try:
444 a = os.stat(os.environ['PWD'])
445 b = os.stat(os.getcwd())
446 if a.st_ino == b.st_ino and a.st_dev == b.st_dev:
447 cwd = os.environ['PWD']
448 else:
449 cwd = os.getcwd()
450 except Exception:
451 cwd = os.getcwd()
452 return cwd
455def http_date(timestamp=None):
456 """Return the current date and time formatted for a message header."""
457 if timestamp is None:
458 timestamp = time.time()
459 s = email.utils.formatdate(timestamp, localtime=False, usegmt=True)
460 return s
463def is_hoppish(header):
464 return header.lower().strip() in hop_headers
467def daemonize(enable_stdio_inheritance=False):
468 """\
469 Standard daemonization of a process.
470 http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7
471 """
472 if 'GUNICORN_FD' not in os.environ:
473 if os.fork():
474 os._exit(0)
475 os.setsid()
477 if os.fork():
478 os._exit(0)
480 os.umask(0o22)
482 # In both the following any file descriptors above stdin
483 # stdout and stderr are left untouched. The inheritance
484 # option simply allows one to have output go to a file
485 # specified by way of shell redirection when not wanting
486 # to use --error-log option.
488 if not enable_stdio_inheritance:
489 # Remap all of stdin, stdout and stderr on to
490 # /dev/null. The expectation is that users have
491 # specified the --error-log option.
493 closerange(0, 3)
495 fd_null = os.open(REDIRECT_TO, os.O_RDWR)
496 # PEP 446, make fd for /dev/null inheritable
497 os.set_inheritable(fd_null, True)
499 # expect fd_null to be always 0 here, but in-case not ...
500 if fd_null != 0:
501 os.dup2(fd_null, 0)
503 os.dup2(fd_null, 1)
504 os.dup2(fd_null, 2)
506 else:
507 fd_null = os.open(REDIRECT_TO, os.O_RDWR)
509 # Always redirect stdin to /dev/null as we would
510 # never expect to need to read interactive input.
512 if fd_null != 0:
513 os.close(0)
514 os.dup2(fd_null, 0)
516 # If stdout and stderr are still connected to
517 # their original file descriptors we check to see
518 # if they are associated with terminal devices.
519 # When they are we map them to /dev/null so that
520 # are still detached from any controlling terminal
521 # properly. If not we preserve them as they are.
522 #
523 # If stdin and stdout were not hooked up to the
524 # original file descriptors, then all bets are
525 # off and all we can really do is leave them as
526 # they were.
527 #
528 # This will allow 'gunicorn ... > output.log 2>&1'
529 # to work with stdout/stderr going to the file
530 # as expected.
531 #
532 # Note that if using --error-log option, the log
533 # file specified through shell redirection will
534 # only be used up until the log file specified
535 # by the option takes over. As it replaces stdout
536 # and stderr at the file descriptor level, then
537 # anything using stdout or stderr, including having
538 # cached a reference to them, will still work.
540 def redirect(stream, fd_expect):
541 try:
542 fd = stream.fileno()
543 if fd == fd_expect and stream.isatty():
544 os.close(fd)
545 os.dup2(fd_null, fd)
546 except AttributeError:
547 pass
549 redirect(sys.stdout, 1)
550 redirect(sys.stderr, 2)
553def seed():
554 try:
555 random.seed(os.urandom(64))
556 except NotImplementedError:
557 random.seed('%s.%s' % (time.time(), os.getpid()))
560def check_is_writable(path):
561 try:
562 with open(path, 'a') as f:
563 f.close()
564 except OSError as e:
565 raise RuntimeError("Error: '%s' isn't writable [%r]" % (path, e))
568def to_bytestring(value, encoding="utf8"):
569 """Converts a string argument to a byte string"""
570 if isinstance(value, bytes):
571 return value
572 if not isinstance(value, str):
573 raise TypeError('%r is not a string' % value)
575 return value.encode(encoding)
578def has_fileno(obj):
579 if not hasattr(obj, "fileno"):
580 return False
582 # check BytesIO case and maybe others
583 try:
584 obj.fileno()
585 except (AttributeError, OSError, io.UnsupportedOperation):
586 return False
588 return True
591def warn(msg):
592 print("!!!", file=sys.stderr)
594 lines = msg.splitlines()
595 for i, line in enumerate(lines):
596 if i == 0:
597 line = "WARNING: %s" % line
598 print("!!! %s" % line, file=sys.stderr)
600 print("!!!\n", file=sys.stderr)
601 sys.stderr.flush()
604def make_fail_app(msg):
605 msg = to_bytestring(msg)
607 def app(environ, start_response):
608 start_response("500 Internal Server Error", [
609 ("Content-Type", "text/plain"),
610 ("Content-Length", str(len(msg)))
611 ])
612 return [msg]
614 return app
617def split_request_uri(uri):
618 if uri.startswith("//"):
619 # When the path starts with //, urlsplit considers it as a
620 # relative uri while the RFC says we should consider it as abs_path
621 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
622 # We use temporary dot prefix to workaround this behaviour
623 parts = urllib.parse.urlsplit("." + uri)
624 return parts._replace(path=parts.path[1:])
626 return urllib.parse.urlsplit(uri)
629# From six.reraise
630def reraise(tp, value, tb=None):
631 try:
632 if value is None:
633 value = tp()
634 if value.__traceback__ is not tb:
635 raise value.with_traceback(tb)
636 raise value
637 finally:
638 value = None
639 tb = None
642def bytes_to_str(b):
643 if isinstance(b, str):
644 return b
645 return str(b, 'latin1')
648def unquote_to_wsgi_str(string):
649 return urllib.parse.unquote_to_bytes(string).decode('latin-1')