diff -urNp '--exclude=*~' '--exclude=build' '--exclude=*pycache*' '--exclude=*package.*note*' '--exclude=*pot' '--exclude=*package_note*' '--exclude=*.orig' gphotoframe-2.0.2-hg2084299dffb6.orig/lib/twisted/web/client.py gphotoframe-2.0.2-hg2084299dffb6/lib/twisted/web/client.py
--- gphotoframe-2.0.2-hg2084299dffb6.orig/lib/twisted/web/client.py 1970-01-01 09:00:00.000000000 +0900
+++ gphotoframe-2.0.2-hg2084299dffb6/lib/twisted/web/client.py 2022-08-03 16:08:30.217342474 +0900
@@ -0,0 +1,736 @@
+# -*- test-case-name: twisted.web.test.test_webclient,twisted.web.test.test_agent -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+HTTP client.
+"""
+
+
+import os
+import collections
+import warnings
+
+from urllib.parse import urljoin, urldefrag
+from urllib.parse import urlunparse as _urlunparse
+
+import zlib
+from functools import wraps
+
+from zope.interface import implementer
+
+from twisted.python.compat import nativeString, networkString
+from twisted.python.deprecate import deprecatedModuleAttribute, deprecated
+from twisted.python.failure import Failure
+from incremental import Version
+
+from twisted.web.iweb import IPolicyForHTTPS, IAgentEndpointFactory
+from twisted.python.deprecate import getDeprecationWarningString
+from twisted.web import http
+from twisted.internet import defer, protocol, task
+from twisted.internet.abstract import isIPv6Address
+from twisted.internet.interfaces import IProtocol, IOpenSSLContextFactory
+from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
+from twisted.python.util import InsensitiveDict
+from twisted.python.components import proxyForInterface
+from twisted.web import error
+from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse
+from twisted.web.http_headers import Headers
+from twisted.logger import Logger
+
+from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
+
+
+def urlunparse(parts):
+ result = _urlunparse(tuple([p.decode("charmap") for p in parts]))
+ return result.encode("charmap")
+
+
+class PartialDownloadError(error.Error):
+ """
+ Page was only partially downloaded, we got disconnected in middle.
+
+ @ivar response: All of the response body which was downloaded.
+ """
+
+
+class HTTPPageGetter(http.HTTPClient):
+ """
+ Gets a resource via HTTP, then quits.
+
+ Typically used with L{HTTPClientFactory}. Note that this class does not, by
+ itself, do anything with the response. If you want to download a resource
+ into a file, use L{HTTPPageDownloader} instead.
+
+ @ivar _completelyDone: A boolean indicating whether any further requests are
+ necessary after this one completes in order to provide a result to
+ C{self.factory.deferred}. If it is C{False}, then a redirect is going
+ to be followed. Otherwise, this protocol's connection is the last one
+ before firing the result Deferred. This is used to make sure the result
+ Deferred is only fired after the connection is cleaned up.
+ """
+
+ quietLoss = 0
+ followRedirect = True
+ failed = 0
+
+ _completelyDone = True
+
+ _specialHeaders = {b"host", b"user-agent", b"cookie", b"content-length"}
+
+ def connectionMade(self):
+ method = _ensureValidMethod(getattr(self.factory, "method", b"GET"))
+ self.sendCommand(method, _ensureValidURI(self.factory.path))
+ if self.factory.scheme == b"http" and self.factory.port != 80:
+ host = b"%b:%d" % (self.factory.host, self.factory.port)
+ elif self.factory.scheme == b"https" and self.factory.port != 443:
+ host = b"%b:%d" % (self.factory.host, self.factory.port)
+ else:
+ host = self.factory.host
+ self.sendHeader(b"Host", self.factory.headers.get(b"host", host))
+ self.sendHeader(b"User-Agent", self.factory.agent)
+ data = getattr(self.factory, "postdata", None)
+ if data is not None:
+ self.sendHeader(b"Content-Length", b"%d" % (len(data),))
+
+ cookieData = []
+ for (key, value) in self.factory.headers.items():
+ if key.lower() not in self._specialHeaders:
+ # we calculated it on our own
+ self.sendHeader(key, value)
+ if key.lower() == b"cookie":
+ cookieData.append(value)
+ for cookie, cookval in self.factory.cookies.items():
+ cookieData.append(cookie + b"=" + cookval)
+ if cookieData:
+ self.sendHeader(b"Cookie", b"; ".join(cookieData))
+ self.endHeaders()
+ self.headers = {}
+
+ if data is not None:
+ self.transport.write(data)
+
+ def handleHeader(self, key, value):
+ """
+ Called every time a header is received. Stores the header information
+ as key-value pairs in the C{headers} attribute.
+
+ @type key: C{str}
+ @param key: An HTTP header field name.
+
+ @type value: C{str}
+ @param value: An HTTP header field value.
+ """
+ key = key.lower()
+ l = self.headers.setdefault(key, [])
+ l.append(value)
+
+ def handleStatus(self, version, status, message):
+ """
+ Handle the HTTP status line.
+
+ @param version: The HTTP version.
+ @type version: L{bytes}
+ @param status: The HTTP status code, an integer represented as a
+ bytestring.
+ @type status: L{bytes}
+ @param message: The HTTP status message.
+ @type message: L{bytes}
+ """
+ self.version, self.status, self.message = version, status, message
+ self.factory.gotStatus(version, status, message)
+
+ def handleEndHeaders(self):
+ self.factory.gotHeaders(self.headers)
+ m = getattr(
+ self, "handleStatus_" + nativeString(self.status), self.handleStatusDefault
+ )
+ m()
+
+ def handleStatus_200(self):
+ pass
+
+ handleStatus_201 = lambda self: self.handleStatus_200()
+ handleStatus_202 = lambda self: self.handleStatus_200()
+
+ def handleStatusDefault(self):
+ self.failed = 1
+
+ def handleStatus_301(self):
+ l = self.headers.get(b"location")
+ if not l:
+ self.handleStatusDefault()
+ return
+ url = l[0]
+ if self.followRedirect:
+ self.factory._redirectCount += 1
+ if self.factory._redirectCount >= self.factory.redirectLimit:
+ err = error.InfiniteRedirection(
+ self.status, b"Infinite redirection detected", location=url
+ )
+ self.factory.noPage(Failure(err))
+ self.quietLoss = True
+ self.transport.loseConnection()
+ return
+
+ self._completelyDone = False
+ self.factory.setURL(url)
+
+ from twisted.internet import reactor
+
+ if self.factory.scheme == b"https":
+ from twisted.internet import ssl
+
+ contextFactory = ssl.ClientContextFactory()
+ reactor.connectSSL(
+ nativeString(self.factory.host),
+ self.factory.port,
+ self.factory,
+ contextFactory,
+ )
+ else:
+ reactor.connectTCP(
+ nativeString(self.factory.host), self.factory.port, self.factory
+ )
+ else:
+ self.handleStatusDefault()
+ self.factory.noPage(
+ Failure(error.PageRedirect(self.status, self.message, location=url))
+ )
+ self.quietLoss = True
+ self.transport.loseConnection()
+
+ def handleStatus_302(self):
+ if self.afterFoundGet:
+ self.handleStatus_303()
+ else:
+ self.handleStatus_301()
+
+ def handleStatus_303(self):
+ self.factory.method = b"GET"
+ self.handleStatus_301()
+
+ def connectionLost(self, reason):
+ """
+ When the connection used to issue the HTTP request is closed, notify the
+ factory if we have not already, so it can produce a result.
+ """
+ if not self.quietLoss:
+ http.HTTPClient.connectionLost(self, reason)
+ self.factory.noPage(reason)
+ if self._completelyDone:
+ # Only if we think we're completely done do we tell the factory that
+ # we're "disconnected". This way when we're following redirects,
+ # only the last protocol used will fire the _disconnectedDeferred.
+ self.factory._disconnectedDeferred.callback(None)
+
+ def handleResponse(self, response):
+ if self.quietLoss:
+ return
+ if self.failed:
+ self.factory.noPage(
+ Failure(error.Error(self.status, self.message, response))
+ )
+ if self.factory.method == b"HEAD":
+ # Callback with empty string, since there is never a response
+ # body for HEAD requests.
+ self.factory.page(b"")
+ elif self.length != None and self.length != 0:
+ self.factory.noPage(
+ Failure(PartialDownloadError(self.status, self.message, response))
+ )
+ else:
+ self.factory.page(response)
+ # server might be stupid and not close connection. admittedly
+ # the fact we do only one request per connection is also
+ # stupid...
+ self.transport.loseConnection()
+
+ def timeout(self):
+ self.quietLoss = True
+ self.transport.abortConnection()
+ self.factory.noPage(
+ defer.TimeoutError(
+ "Getting %s took longer than %s seconds."
+ % (self.factory.url, self.factory.timeout)
+ )
+ )
+
+
+class HTTPPageDownloader(HTTPPageGetter):
+
+ transmittingPage = 0
+
+ def handleStatus_200(self, partialContent=0):
+ HTTPPageGetter.handleStatus_200(self)
+ self.transmittingPage = 1
+ self.factory.pageStart(partialContent)
+
+ def handleStatus_206(self):
+ self.handleStatus_200(partialContent=1)
+
+ def handleResponsePart(self, data):
+ if self.transmittingPage:
+ self.factory.pagePart(data)
+
+ def handleResponseEnd(self):
+ if self.length:
+ self.transmittingPage = 0
+ self.factory.noPage(Failure(PartialDownloadError(self.status)))
+ if self.transmittingPage:
+ self.factory.pageEnd()
+ self.transmittingPage = 0
+ if self.failed:
+ self.factory.noPage(Failure(error.Error(self.status, self.message, None)))
+ self.transport.loseConnection()
+
+
+class HTTPClientFactory(protocol.ClientFactory):
+ """Download a given URL.
+
+ @type deferred: Deferred
+ @ivar deferred: A Deferred that will fire when the content has
+ been retrieved. Once this is fired, the ivars `status', `version',
+ and `message' will be set.
+
+ @type status: bytes
+ @ivar status: The status of the response.
+
+ @type version: bytes
+ @ivar version: The version of the response.
+
+ @type message: bytes
+ @ivar message: The text message returned with the status.
+
+ @type response_headers: dict
+ @ivar response_headers: The headers that were specified in the
+ response from the server.
+
+ @type method: bytes
+ @ivar method: The HTTP method to use in the request. This should be one of
+ OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, or CONNECT (case
+ matters). Other values may be specified if the server being contacted
+ supports them.
+
+ @type redirectLimit: int
+ @ivar redirectLimit: The maximum number of HTTP redirects that can occur
+ before it is assumed that the redirection is endless.
+
+ @type afterFoundGet: C{bool}
+ @ivar afterFoundGet: Deviate from the HTTP 1.1 RFC by handling redirects
+ the same way as most web browsers; if the request method is POST and a
+ 302 status is encountered, the redirect is followed with a GET method
+
+ @type _redirectCount: int
+ @ivar _redirectCount: The current number of HTTP redirects encountered.
+
+ @ivar _disconnectedDeferred: A L{Deferred} which only fires after the last
+ connection associated with the request (redirects may cause multiple
+ connections to be required) has closed. The result Deferred will only
+ fire after this Deferred, so that callers can be assured that there are
+ no more event sources in the reactor once they get the result.
+ """
+
+ protocol = HTTPPageGetter
+
+ url = None
+ scheme = None
+ host = b""
+ port = None
+ path = None
+
+ def __init__(
+ self,
+ url,
+ method=b"GET",
+ postdata=None,
+ headers=None,
+ agent=b"Twisted PageGetter",
+ timeout=0,
+ cookies=None,
+ followRedirect=True,
+ redirectLimit=20,
+ afterFoundGet=False,
+ ):
+ self.followRedirect = followRedirect
+ self.redirectLimit = redirectLimit
+ self._redirectCount = 0
+ self.timeout = timeout
+ self.agent = agent
+ self.afterFoundGet = afterFoundGet
+ if cookies is None:
+ cookies = {}
+ self.cookies = cookies
+ if headers is not None:
+ self.headers = InsensitiveDict(headers)
+ else:
+ self.headers = InsensitiveDict()
+ if postdata is not None:
+ self.headers.setdefault(b"Content-Length", b"%d" % (len(postdata),))
+ # just in case a broken http/1.1 decides to keep connection alive
+ self.headers.setdefault(b"connection", b"close")
+ self.postdata = postdata
+ self.method = _ensureValidMethod(method)
+
+ self.setURL(url)
+
+ self.waiting = 1
+ self._disconnectedDeferred = defer.Deferred()
+ self.deferred = defer.Deferred()
+ # Make sure the first callback on the result Deferred pauses the
+ # callback chain until the request connection is closed.
+ self.deferred.addBoth(self._waitForDisconnect)
+ self.response_headers = None
+
+ def _waitForDisconnect(self, passthrough):
+ """
+ Chain onto the _disconnectedDeferred, preserving C{passthrough}, so that
+ the result is only available after the associated connection has been
+ closed.
+ """
+ self._disconnectedDeferred.addCallback(lambda ignored: passthrough)
+ return self._disconnectedDeferred
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__}: {self.url}>"
+
+ def setURL(self, url):
+ _ensureValidURI(url.strip())
+ self.url = url
+ uri = URI.fromBytes(url)
+ if uri.scheme and uri.host:
+ self.scheme = uri.scheme
+ self.host = uri.host
+ self.port = uri.port
+ self.path = uri.originForm
+
+ def buildProtocol(self, addr):
+ p = protocol.ClientFactory.buildProtocol(self, addr)
+ p.followRedirect = self.followRedirect
+ p.afterFoundGet = self.afterFoundGet
+ if self.timeout:
+ from twisted.internet import reactor
+
+ timeoutCall = reactor.callLater(self.timeout, p.timeout)
+ self.deferred.addBoth(self._cancelTimeout, timeoutCall)
+ return p
+
+ def _cancelTimeout(self, result, timeoutCall):
+ if timeoutCall.active():
+ timeoutCall.cancel()
+ return result
+
+ def gotHeaders(self, headers):
+ """
+ Parse the response HTTP headers.
+
+ @param headers: The response HTTP headers.
+ @type headers: L{dict}
+ """
+ self.response_headers = headers
+ if b"set-cookie" in headers:
+ for cookie in headers[b"set-cookie"]:
+ if b"=" in cookie:
+ cookparts = cookie.split(b";")
+ cook = cookparts[0]
+ cook.lstrip()
+ k, v = cook.split(b"=", 1)
+ self.cookies[k.lstrip()] = v.lstrip()
+
+ def gotStatus(self, version, status, message):
+ """
+ Set the status of the request on us.
+
+ @param version: The HTTP version.
+ @type version: L{bytes}
+ @param status: The HTTP status code, an integer represented as a
+ bytestring.
+ @type status: L{bytes}
+ @param message: The HTTP status message.
+ @type message: L{bytes}
+ """
+ self.version, self.status, self.message = version, status, message
+
+ def page(self, page):
+ if self.waiting:
+ self.waiting = 0
+ self.deferred.callback(page)
+
+ def noPage(self, reason):
+ if self.waiting:
+ self.waiting = 0
+ self.deferred.errback(reason)
+
+ def clientConnectionFailed(self, _, reason):
+ """
+ When a connection attempt fails, the request cannot be issued. If no
+ result has yet been provided to the result Deferred, provide the
+ connection failure reason as an error result.
+ """
+ if self.waiting:
+ self.waiting = 0
+ # If the connection attempt failed, there is nothing more to
+ # disconnect, so just fire that Deferred now.
+ self._disconnectedDeferred.callback(None)
+ self.deferred.errback(reason)
+
+
+class HTTPDownloader(HTTPClientFactory):
+ """
+ Download to a file.
+ """
+
+ protocol = HTTPPageDownloader
+ value = None
+ _log = Logger()
+
+ def __init__(
+ self,
+ url,
+ fileOrName,
+ method=b"GET",
+ postdata=None,
+ headers=None,
+ agent=b"Twisted client",
+ supportPartial=False,
+ timeout=0,
+ cookies=None,
+ followRedirect=True,
+ redirectLimit=20,
+ afterFoundGet=False,
+ ):
+ self.requestedPartial = 0
+ if isinstance(fileOrName, str):
+ self.fileName = fileOrName
+ self.file = None
+ if supportPartial and os.path.exists(self.fileName):
+ fileLength = os.path.getsize(self.fileName)
+ if fileLength:
+ self.requestedPartial = fileLength
+ if headers is None:
+ headers = {}
+ headers[b"range"] = b"bytes=%d-" % (fileLength,)
+ else:
+ self.file = fileOrName
+ HTTPClientFactory.__init__(
+ self,
+ url,
+ method=method,
+ postdata=postdata,
+ headers=headers,
+ agent=agent,
+ timeout=timeout,
+ cookies=cookies,
+ followRedirect=followRedirect,
+ redirectLimit=redirectLimit,
+ afterFoundGet=afterFoundGet,
+ )
+
+ def gotHeaders(self, headers):
+ HTTPClientFactory.gotHeaders(self, headers)
+ if self.requestedPartial:
+ contentRange = headers.get(b"content-range", None)
+ if not contentRange:
+ # server doesn't support partial requests, oh well
+ self.requestedPartial = 0
+ return
+ start, end, realLength = http.parseContentRange(contentRange[0])
+ if start != self.requestedPartial:
+ # server is acting weirdly
+ self.requestedPartial = 0
+
+ def openFile(self, partialContent):
+ if partialContent:
+ file = open(self.fileName, "rb+")
+ file.seek(0, 2)
+ else:
+ file = open(self.fileName, "wb")
+ return file
+
+ def pageStart(self, partialContent):
+ """Called on page download start.
+
+ @param partialContent: tells us if the download is partial download we requested.
+ """
+ if partialContent and not self.requestedPartial:
+ raise ValueError(
+ "we shouldn't get partial content response if we didn't want it!"
+ )
+ if self.waiting:
+ try:
+ if not self.file:
+ self.file = self.openFile(partialContent)
+ except OSError:
+ # raise
+ self.deferred.errback(Failure())
+
+ def pagePart(self, data):
+ if not self.file:
+ return
+ try:
+ self.file.write(data)
+ except OSError:
+ # raise
+ self.file = None
+ self.deferred.errback(Failure())
+
+ def noPage(self, reason):
+ """
+ Close the storage file and errback the waiting L{Deferred} with the
+ given reason.
+ """
+ if self.waiting:
+ self.waiting = 0
+ if self.file:
+ try:
+ self.file.close()
+ except BaseException:
+ self._log.failure("Error closing HTTPDownloader file")
+ self.deferred.errback(reason)
+
+ def pageEnd(self):
+ self.waiting = 0
+ if not self.file:
+ return
+ try:
+ self.file.close()
+ except OSError:
+ self.deferred.errback(Failure())
+ return
+ self.deferred.callback(self.value)
+
+
+class URI:
+ """
+ A URI object.
+
+ @see: U{https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21}
+ """
+
+ def __init__(self, scheme, netloc, host, port, path, params, query, fragment):
+ """
+ @type scheme: L{bytes}
+ @param scheme: URI scheme specifier.
+
+ @type netloc: L{bytes}
+ @param netloc: Network location component.
+
+ @type host: L{bytes}
+ @param host: Host name. For IPv6 address literals the brackets are
+ stripped.
+
+ @type port: L{int}
+ @param port: Port number.
+
+ @type path: L{bytes}
+ @param path: Hierarchical path.
+
+ @type params: L{bytes}
+ @param params: Parameters for last path segment.
+
+ @type query: L{bytes}
+ @param query: Query string.
+
+ @type fragment: L{bytes}
+ @param fragment: Fragment identifier.
+ """
+ self.scheme = scheme
+ self.netloc = netloc
+ self.host = host.strip(b"[]")
+ self.port = port
+ self.path = path
+ self.params = params
+ self.query = query
+ self.fragment = fragment
+
+ @classmethod
+ def fromBytes(cls, uri, defaultPort=None):
+ """
+ Parse the given URI into a L{URI}.
+
+ @type uri: C{bytes}
+ @param uri: URI to parse.
+
+ @type defaultPort: C{int} or L{None}
+ @param defaultPort: An alternate value to use as the port if the URI
+ does not include one.
+
+ @rtype: L{URI}
+ @return: Parsed URI instance.
+ """
+ uri = uri.strip()
+ scheme, netloc, path, params, query, fragment = http.urlparse(uri)
+
+ if defaultPort is None:
+ if scheme == b"https":
+ defaultPort = 443
+ else:
+ defaultPort = 80
+
+ if b":" in netloc:
+ host, port = netloc.rsplit(b":", 1)
+ try:
+ port = int(port)
+ except ValueError:
+ host, port = netloc, defaultPort
+ else:
+ host, port = netloc, defaultPort
+ return cls(scheme, netloc, host, port, path, params, query, fragment)
+
+
+ @property
+ def originForm(self):
+ """
+ The absolute I{URI} path including I{URI} parameters, query string and
+ fragment identifier.
+
+ @see: U{https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21#section-5.3}
+
+ @return: The absolute path in original form.
+ @rtype: L{bytes}
+ """
+ # The HTTP bis draft says the origin form should not include the
+ # fragment.
+ path = urlunparse((b"", b"", self.path, self.params, self.query, b""))
+ if path == b"":
+ path = b"/"
+ return path
+
+
+_GETPAGE_REPLACEMENT_TEXT = "https://pypi.org/project/treq/ or twisted.web.client.Agent"
+
+
+def _deprecateGetPageClasses():
+ """
+ Mark the protocols and factories associated with L{getPage} and
+ L{downloadPage} as deprecated.
+ """
+ for klass in [
+ HTTPPageGetter,
+ HTTPPageDownloader,
+ HTTPClientFactory,
+ HTTPDownloader,
+ ]:
+ deprecatedModuleAttribute(
+ Version("Twisted", 16, 7, 0),
+ getDeprecationWarningString(
+ klass,
+ Version("Twisted", 16, 7, 0),
+ replacement=_GETPAGE_REPLACEMENT_TEXT,
+ ).split("; ")[1],
+ klass.__module__,
+ klass.__name__,
+ )
+
+
+_deprecateGetPageClasses()
+
+__all__ = [
+ "HTTPClientFactory",
+ "HTTPDownloader",
+ "HTTPPageDownloader",
+ "HTTPPageGetter",
+ "PartialDownloadError",
+ "Response",
+ "URI",
+]
diff -urNp '--exclude=*~' '--exclude=build' '--exclude=*pycache*' '--exclude=*package.*note*' '--exclude=*pot' '--exclude=*package_note*' '--exclude=*.orig' gphotoframe-2.0.2-hg2084299dffb6.orig/lib/utils/urlget.py gphotoframe-2.0.2-hg2084299dffb6/lib/utils/urlget.py
--- gphotoframe-2.0.2-hg2084299dffb6.orig/lib/utils/urlget.py 2021-03-17 00:02:28.218427525 +0900
+++ gphotoframe-2.0.2-hg2084299dffb6/lib/utils/urlget.py 2022-08-03 16:08:30.219342467 +0900
@@ -29,7 +29,7 @@ HTTP Client with proxy support.
import os
from urllib.parse import urlunparse
-from twisted.web import client
+from ..twisted.web import client
from twisted.internet import reactor
from twisted.web import http
diff -urNp '--exclude=*~' '--exclude=build' '--exclude=*pycache*' '--exclude=*package.*note*' '--exclude=*pot' '--exclude=*package_note*' '--exclude=*.orig' gphotoframe-2.0.2-hg2084299dffb6.orig/setup.py gphotoframe-2.0.2-hg2084299dffb6/setup.py
--- gphotoframe-2.0.2-hg2084299dffb6.orig/setup.py 2021-03-13 15:27:48.150281140 +0900
+++ gphotoframe-2.0.2-hg2084299dffb6/setup.py 2022-08-03 16:17:35.241754522 +0900
@@ -25,6 +25,7 @@ setup(name = 'gphotoframe',
license = 'GPL3',
package_dir = {'gphotoframe' : 'lib'},
packages = ['gphotoframe', 'gphotoframe.utils', 'gphotoframe.dbus',
+ 'gphotoframe.twisted', 'gphotoframe.twisted.web',
'gphotoframe.utils.oauth',
'gphotoframe.image', 'gphotoframe.image.actor',
'gphotoframe.preferences', 'gphotoframe.history',