Exemplo n.º 1
0
    def modify_request(self, http_request):
        """Changes the HTTP request before sending it to the server.
    
    Sets the User-Agent HTTP header and fills in the HTTP host portion
    of the URL if one was not included in the request (for this it uses
    the self.host member if one is set). This method is called in
    self.request.

    Args:
      http_request: An atom_http_core.HttpRequest() (optional) If one is
                    not provided, a new HttpRequest is instantiated.

    Returns:
      An atom_http_core.HttpRequest() with the User-Agent header set and
      if this client has a value in its host member, the host in the request
      URL is set.
    """
        if http_request is None:
            http_request = atom_http_core.HttpRequest()

        if self.host is not None and http_request.uri.host is None:
            http_request.uri.host = self.host

        # Set the user agent header for logging purposes.
        if self.source:
            http_request.headers[
                'User-Agent'] = '%s gdata-py/2.0.10' % self.source
        else:
            http_request.headers['User-Agent'] = 'gdata-py/2.0.10'

        return http_request
Exemplo n.º 2
0
    def update(self, entry, auth_token=None, force=False, **kwargs):
        """Edits the entry on the server by sending the XML for this entry.

    Performs a PUT and converts the response to a new entry object with a
    matching class to the entry passed in.

    Args:
      entry:
      auth_token:
      force: boolean stating whether an update should be forced. Defaults to
             False. Normally, if a change has been made since the passed in
             entry was obtained, the server will not overwrite the entry since
             the changes were based on an obsolete version of the entry.
             Setting force to True will cause the update to silently
             overwrite whatever version is present.

    Returns:
      A new Entry object of a matching type to the entry which was passed in.
    """
        http_request = atom_http_core.HttpRequest()
        http_request.add_body_part(
            entry.to_string(get_xml_version(self.api_version)),
            'application/atom+xml')
        # Include the ETag in the request if present.
        if force:
            http_request.headers['If-Match'] = '*'
        elif hasattr(entry, 'etag') and entry.etag:
            http_request.headers['If-Match'] = entry.etag

        return self.request(method='PUT',
                            uri=entry.find_edit_link(),
                            auth_token=auth_token,
                            http_request=http_request,
                            desired_class=entry.__class__,
                            **kwargs)
Exemplo n.º 3
0
    def _init_session(self,
                      resumable_media_link,
                      entry=None,
                      headers=None,
                      auth_token=None):
        """Starts a new resumable upload to a service that supports the protocol.

    The method makes a request to initiate a new upload session. The unique
    upload uri returned by the server (and set in this method) should be used
    to send upload chunks to the server.

    Args:
      resumable_media_link: str The full URL for the #resumable-create-media or
          #resumable-edit-media link for starting a resumable upload request or
          updating media using a resumable PUT.
      entry: A (optional) gdata_data.GDEntry containging metadata to create the 
          upload from.
      headers: dict (optional) Additional headers to send in the initial request
          to create the resumable upload request. These headers will override
          any default headers sent in the request. For example:
          headers={'Slug': 'MyTitle'}.
      auth_token: (optional) An object which sets the Authorization HTTP header
          in its modify_request method. Recommended classes include
          gdata_gauth.ClientLoginToken and gdata_gauth.AuthSubToken
          among others.

    Returns:
      The final Atom entry as created on the server. The entry will be
      parsed accoring to the class specified in self.desired_class.

    Raises:
      RequestError if the unique upload uri is not set or the
      server returns something other than an HTTP 308 when the upload is
      incomplete.
    """
        http_request = atom_http_core.HttpRequest()

        # Send empty POST if Atom XML wasn't specified.
        if entry is None:
            http_request.add_body_part('', self.content_type, size=0)
        else:
            http_request.add_body_part(str(entry),
                                       'application/atom+xml',
                                       size=len(str(entry)))
        http_request.headers['X-Upload-Content-Type'] = self.content_type
        http_request.headers['X-Upload-Content-Length'] = self.total_file_size

        if headers is not None:
            http_request.headers.update(headers)

        response = self.client.request(method='POST',
                                       uri=resumable_media_link,
                                       auth_token=auth_token,
                                       http_request=http_request)

        self.upload_uri = (response.getheader('location')
                           or response.getheader('Location'))
Exemplo n.º 4
0
 def get_entry(self,
               uri,
               auth_token=None,
               converter=None,
               desired_class=gdata_data.GDEntry,
               etag=None,
               **kwargs):
     http_request = atom_http_core.HttpRequest()
     # Conditional retrieval
     if etag is not None:
         http_request.headers['If-None-Match'] = etag
     return self.request(method='GET',
                         uri=uri,
                         auth_token=auth_token,
                         http_request=http_request,
                         converter=converter,
                         desired_class=desired_class,
                         **kwargs)
Exemplo n.º 5
0
    def upload_chunk(self, start_byte, content_bytes):
        """Uploads a byte range (chunk) to the resumable upload server.

    Args:
      start_byte: int The byte offset of the total file where the byte range
          passed in lives.
      content_bytes: str The file contents of this chunk.

    Returns:
      The final Atom entry created on the server. The entry object's type will
      be the class specified in self.desired_class.

    Raises:
      RequestError if the unique upload uri is not set or the
      server returns something other than an HTTP 308 when the upload is
      incomplete.
    """
        if self.upload_uri is None:
            raise RequestError('Resumable upload request not initialized.')

        # Adjustment if last byte range is less than defined chunk size.
        chunk_size = self.chunk_size
        if len(content_bytes) <= chunk_size:
            chunk_size = len(content_bytes)

        http_request = atom_http_core.HttpRequest()
        http_request.add_body_part(content_bytes,
                                   self.content_type,
                                   size=len(content_bytes))
        http_request.headers['Content-Range'] = (
            'bytes %s-%s/%s' %
            (start_byte, start_byte + chunk_size - 1, self.total_file_size))

        try:
            response = self.client.request(method='POST',
                                           uri=self.upload_uri,
                                           http_request=http_request,
                                           desired_class=self.desired_class)
            return response
        except RequestError, error:
            if error.status == 308:
                return None
            else:
                raise error
Exemplo n.º 6
0
 def post(self,
          entry,
          uri,
          auth_token=None,
          converter=None,
          desired_class=None,
          **kwargs):
     if converter is None and desired_class is None:
         desired_class = entry.__class__
     http_request = atom_http_core.HttpRequest()
     http_request.add_body_part(
         entry.to_string(get_xml_version(self.api_version)),
         'application/atom+xml')
     return self.request(method='POST',
                         uri=uri,
                         auth_token=auth_token,
                         http_request=http_request,
                         converter=converter,
                         desired_class=desired_class,
                         **kwargs)
Exemplo n.º 7
0
    def query_upload_status(self, uri=None):
        """Queries the current status of a resumable upload request.

    Args:
      uri: str (optional) A resumable upload uri to query and override the one
          that is set in this object.

    Returns:
      An integer representing the file position (byte) to resume the upload from
      or True if the upload is complete.

    Raises:
      RequestError if anything other than a HTTP 308 is returned
      when the request raises an exception.
    """
        # Override object's unique upload uri.
        if uri is None:
            uri = self.upload_uri

        http_request = atom_http_core.HttpRequest()
        http_request.headers['Content-Length'] = '0'
        http_request.headers[
            'Content-Range'] = 'bytes */%s' % self.total_file_size

        try:
            response = self.client.request(method='POST',
                                           uri=uri,
                                           http_request=http_request)
            if response.status == 201:
                return True
            else:
                raise error_from_response(
                    '%s returned by server' % response.status, response,
                    RequestError)
        except RequestError, error:
            if error.status == 308:
                for pair in error.headers:
                    if pair[0].capitalize() == 'Range':
                        return int(pair[1].split('-')[1]) + 1
            else:
                raise error
Exemplo n.º 8
0
    def delete(self, entry_or_uri, auth_token=None, force=False, **kwargs):
        http_request = atom_http_core.HttpRequest()

        # Include the ETag in the request if present.
        if force:
            http_request.headers['If-Match'] = '*'
        elif hasattr(entry_or_uri, 'etag') and entry_or_uri.etag:
            http_request.headers['If-Match'] = entry_or_uri.etag

        # If the user passes in a URL, just delete directly, may not work as
        # the service might require an ETag.
        if isinstance(entry_or_uri, (str, unicode, atom_http_core.Uri)):
            return self.request(method='DELETE',
                                uri=entry_or_uri,
                                http_request=http_request,
                                auth_token=auth_token,
                                **kwargs)

        return self.request(method='DELETE',
                            uri=entry_or_uri.find_edit_link(),
                            http_request=http_request,
                            auth_token=auth_token,
                            **kwargs)
Exemplo n.º 9
0
    def request_client_login_token(
            self,
            email,
            password,
            source,
            service=None,
            account_type='HOSTED_OR_GOOGLE',
            auth_url=atom_http_core.Uri.parse_uri(
                'https://www.google.com/accounts/ClientLogin'),
            captcha_token=None,
            captcha_response=None):
        service = service or self.auth_service
        # Set the target URL.
        http_request = atom_http_core.HttpRequest(uri=auth_url, method='POST')
        http_request.add_body_part(
            gdata_gauth.generate_client_login_request_body(
                email=email,
                password=password,
                service=service,
                source=source,
                account_type=account_type,
                captcha_token=captcha_token,
                captcha_response=captcha_response),
            'application/x-www-form-urlencoded')

        # Use the underlying http_client to make the request.
        response = self.http_client.request(http_request)

        response_body = response.read()
        if response.status == 200:
            token_string = gdata_gauth.get_client_login_token_string(
                response_body)
            if token_string is not None:
                return gdata_gauth.ClientLoginToken(token_string)
            else:
                raise ClientLoginTokenMissing(
                    'Recieved a 200 response to client login request,'
                    ' but no token was present. %s' % (response_body, ))
        elif response.status == 403:
            captcha_challenge = gdata_gauth.get_captcha_challenge(
                response_body)
            if captcha_challenge:
                challenge = CaptchaChallenge('CAPTCHA required')
                challenge.captcha_url = captcha_challenge['url']
                challenge.captcha_token = captcha_challenge['token']
                raise challenge
            elif response_body.splitlines()[0] == 'Error=BadAuthentication':
                raise BadAuthentication('Incorrect username or password')
            else:
                raise error_from_response('Server responded with a 403 code',
                                          response, RequestError,
                                          response_body)
        elif response.status == 302:
            # Google tries to redirect all bad URLs back to
            # http://www.google.<locale>. If a redirect
            # attempt is made, assume the user has supplied an incorrect
            # authentication URL
            raise error_from_response('Server responded with a redirect',
                                      response, BadAuthenticationServiceURL,
                                      response_body)
        else:
            raise error_from_response(
                'Server responded to ClientLogin request', response,
                ClientLoginFailed, response_body)
Exemplo n.º 10
0
    def request(self, operation, url, data=None, headers=None):
        """Performs an HTTP call to the server, supports GET, POST, PUT, and 
    DELETE.

    Usage example, perform and HTTP GET on http://www.google.com/:
      import atom.http
      client = atom.http.HttpClient()
      http_response = client.request('GET', 'http://www.google.com/')

    Args:
      operation: str The HTTP operation to be performed. This is usually one
          of 'GET', 'POST', 'PUT', or 'DELETE'
      data: filestream, list of parts, or other object which can be converted
          to a string. Should be set to None when performing a GET or DELETE.
          If data is a file-like object which can be read, this method will 
          read a chunk of 100K bytes at a time and send them. 
          If the data is a list of parts to be sent, each part will be 
          evaluated and sent.
      url: The full URL to which the request should be sent. Can be a string
          or atom_url.Url.
      headers: dict of strings. HTTP headers which should be sent
          in the request. 
    """
        all_headers = self.headers.copy()
        if headers:
            all_headers.update(headers)

        # If the list of headers does not include a Content-Length, attempt to
        # calculate it based on the data object.
        if data and 'Content-Length' not in all_headers:
            if isinstance(data, types.StringTypes):
                all_headers['Content-Length'] = str(len(data))
            else:
                raise atom_http_interface.ContentLengthRequired(
                    'Unable to calculate '
                    'the length of the data parameter. Specify a value for '
                    'Content-Length')

        # Set the content type to the default value if none was set.
        if 'Content-Type' not in all_headers:
            all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE

        if self.v2_http_client is not None:
            http_request = atom_http_core.HttpRequest(method=operation)
            atom_http_core.Uri.parse_uri(str(url)).modify_request(http_request)
            http_request.headers = all_headers
            if data:
                http_request._body_parts.append(data)
            return self.v2_http_client.request(http_request=http_request)

        if not isinstance(url, atom_url.Url):
            if isinstance(url, types.StringTypes):
                url = atom_url.parse_url(url)
            else:
                raise atom_http_interface.UnparsableUrlObject(
                    'Unable to parse url '
                    'parameter because it was not a string or atom_url.Url')

        connection = self._prepare_connection(url, all_headers)

        if self.debug:
            connection.debuglevel = 1

        connection.putrequest(operation,
                              self._get_access_url(url),
                              skip_host=True)
        if url.port is not None:
            connection.putheader('Host', '%s:%s' % (url.host, url.port))
        else:
            connection.putheader('Host', url.host)

        # Overcome a bug in Python 2.4 and 2.5
        # httplib.HTTPConnection.putrequest adding
        # HTTP request header 'Host: www.google.com:443' instead of
        # 'Host: www.google.com', and thus resulting the error message
        # 'Token invalid - AuthSub token has wrong scope' in the HTTP response.
        if (url.protocol == 'https' and int(url.port or 443) == 443
                and hasattr(connection, '_buffer')
                and isinstance(connection._buffer, list)):
            header_line = 'Host: %s:443' % url.host
            replacement_header_line = 'Host: %s' % url.host
            try:
                connection._buffer[connection._buffer.index(header_line)] = (
                    replacement_header_line)
            except ValueError:  # header_line missing from connection._buffer
                pass

        # Send the HTTP headers.
        for header_name in all_headers:
            connection.putheader(header_name, all_headers[header_name])
        connection.endheaders()

        # If there is data, send it in the request.
        if data:
            if isinstance(data, list):
                for data_part in data:
                    _send_data_part(data_part, connection)
            else:
                _send_data_part(data, connection)

        # Return the HTTP Response from the server.
        return connection.getresponse()