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',