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