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