Issues

ZF-10194: Can't delete an entry from Google services using Gdata->delete method - client headers not taken into account ?

Description

Trying to create/update/delete contacts from Google contacts. Know there's no classes as they already exist for others services, but i am using gdata Zend_Gdata_Query class to directly retrieve contacts, gdata->updateEntry() to update a contact

Last action is how to delete a contact.

Looked the documentation (http://files.zend.com/help/Zend-Framework/…) But it didn't work : Got a 403 error with error message "If-Match or If-None-Match header or entry etag attribute required"


Steps to reproduce issue:

Try to delete contact with this php code : $client = Zend_Gdata_ClientLogin::getHttpClient($login,$pass,'cp'); $client->setHeaders("If-Match: *"); $gdata = new Zend_Gdata($client); $gdata->setMajorProtocolVersion(3); $gdata->setMinorProtocolVersion(null);

$gdata->delete('http://google.com/m8/feeds/…('@', '%40', $user->google_email).'/full/'.$synchContacts->idgoogle);

I also tried to retrieve the entry and delete it directly - same result ! $query = new Zend_Gdata_Query('http://google.com/m8/feeds/…('@', '%40', $user->google_email).'/full/'.$synchContacts->idgoogle); $entry = $gdata->getEntry($query); $entry->delete();

I tried on zend framework 10.2 and 10.5 same behavior

I tried to downgrade to protocol version 1, it was working last month but now it isn't anymore. How can we check with Zend or Google if there's some changes in the apis ?

I tried downgrading to protocol V1 but it didn't work as well. Why the setHeaders instruction is not correctly take into account by Google servers ?

I captured the network traffic and don't see the header If-Match I wrote in the code. Is it taken into account ?

Expected output:

Contact deleted, with answer 200 from Google

Actual results:

403

Here is the network traffic recorded between my app and google servers :

My App packet :

DELETE /m8/feeds/contacts/demococea%40captivea.fr/full/308d6f698f2e8298 HTTP/1.1 Host: www.google.com Connection: close User-Agent: MyCompany-MyApp-1.0 Zend_Framework_Gdata/1.10.2 authorization: GoogleLogin auth=DQAAAJ8AAABGra3kyGKzGO_Gpy8ULHhnr3irCWGXcIMsNFL0s5qCoS4ss3O2EHQ8oH5uVvdXI4HX6s9lNfdymnZwrmjCgtPX6KD1YAGtz2AL3cKHWYYGQjLr9xJWoy1Bg_w3x-AzJ21jDeDVltN6Im8gtZDfo0dGx5AGQ9IicTlNiqUvc_17nNOPDSTMQXpDXVoZxqX7OcK_dUJSXoUwPPVcGRlykz9H GData-Version: 3 Accept-encoding: identity Content-Type: application/atom+xml Content-Length: 0

Answer from Google :

HTTP/1.1 403 Forbidden Content-Type: text/html; charset=UTF-8 Date: Wed, 21 Jul 2010 09:56:32 GMT Expires: Wed, 21 Jul 2010 09:56:32 GMT Cache-Control: private, max-age=0 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Server: GSE Connection: close If-Match or If-None-Match header or entry etag attribute required

Comments

Problem is located in Zend_Gdata_App::performHttpRequest (app.php line 639)

// Make sure the HTTP client object is 'clean' before making a request // In addition to standard headers to reset via resetParameters(), // also reset the Slug and If-Match headers $this->_httpClient->resetParameters(); $this->_httpClient->setHeaders(array('Slug', 'If-Match'));

As the If-Match parameters is reseted during the $gData->delete($entry) call, there's no way for the developper to set up the If-Match header required by Google API.

Default behavior should be the following : Case A

if delete call has been made using edit URL parameter (string), then in case of a delete operation we should add a header "if-Match: *" as we can't know the etag attribute

Case B In case we call the delete method with the entry element (from the feed retrieved) then in case of a delete operation we should add a header "If-Match: "

The modification should be in prepareRequest method

Line 500 (Case A) if ($data == null && $method == 'DELETE') { $headers['If-Match'] = '*'; }


Line 531 (Case B) if ($method == 'DELETE') { if ($rawData != null && isset($rawData->etag) && $rawData->etag != '') { $headers['If-Match'] = $rawData->etag; }else{ $headers['If-Match'] = '*'; } }

Thus the delete calls are now working !

Our company are also experiencing this bug with DELETE actions. Framework version 1.11.11.

I can confirm that the approach @Yannick mentions does work. However I have implemented it by extending Zend_Gdata_Calendar into my own class and overriding prepareRequest as follows:


class ZendGdataCalendar extends \Zend_Gdata_Calendar {
    /**
     * Overridden to fix an issue with the HTTP request/response for deleting.
     * @link http://zendframework.com/issues/browse/ZF-10194
     *
     * @param       $method
     * @param null  $url
     * @param array $headers
     * @param null  $data
     * @param null  $contentTypeOverride
     *
     * @return array
     */
    public function prepareRequest($method,
                                   $url = null,
                                   $headers = array(),
                                   $data = null,
                                   $contentTypeOverride = null) {
        $request = parent::prepareRequest($method, $url, $headers, $data, $contentTypeOverride);

        if($request['method'] == 'DELETE') {
            // Default to any
            $request['headers']['If-Match'] = '*';

            if($data instanceof Zend_Gdata_App_MediaSource) {
                $rawData = $data->encode();
                if(isset($rawData->etag) && $rawData->etag != '') {
                    // Set specific match
                    $request['headers']['If-Match'] = $rawData->etag;
                }
            }
        }

        return $request;
    }
}

Hi Tim,

Glad to see that my 1year old work helped you fix this damned issue ! I didn't catch how you implemented it and it may interest me as now I am blocked to 1.10.6 as I didn't had any time upgrading and reporting the fix.

Does it change anything to the php code needed to use the service ? In deed I don't know how the lines :

$entry = $gdata->getEntry($query); $entry->delete();

will return instance of your code instead of default Zend_Gdata_Calendar (ok I'm in a rush right now so I don't have time to think about it but I would be interested to have a solution to upgrade to latest Zend framework !)

Have a nice day.

I'm using my overridden Zend_Gdata_Calendar class with a Symfony 2 project so it's difficult to explain the usage of it without mentioning the Symfony2 service container and that sort of thing.

However, it seems from your code snippet that you are creating an instance of Zend_Gdata_Calendar and assigning it to $gdata. So in that situation you would instantiate the child class instead:

// Create your $httpClient first as that is used for the transport layer $gdata = new ZendGdataCalendar($httpClient);

Also I am using PHP 5.3 namespaces so my class is called ZendGdataCalendar within its own namespace. If you're not using namespacing I'd call the class something different, like: ZendGdataCalendarFixed.

You are the man!!! The case B solved my problem :D, thank you for sharing!!

Hi RChea, glad to see the 2 years old bug report still helps few developpers ! Will this be fixed one day into Zend framework ?

I believe that this patch solves cases A and B as referenced by Yannick.

I would appreciate it if someone could test this patch before I commit it.