I got a call the other day from an engineer at Apple who found a fun bug in iDisk: their WebDAV server reads the PUT Content-length into, yes, a 32-bit signed int, so the only way to send files bigger than 2G is to use Transfer-encoding: chunked. I don't know if y'all have any interest in adding a proper chunked PUT implementation into neon, but here's some code for the curious. The only change to neon was making a req->body_length of -1 signal indeterminate length: I added a conditional if ( length >= 0 ) in set_body_length() to suppress adding the content-length header, and I changed the two instances of req->body_length > 0 in send_request() to req->body_length != 0.

Here's the data provider callback:

        struct uploadData
        {
                WebDAVConnectionWorker* worker;
                off_t size;
                off_t offset;
                int fd;
                BOOL done;
        };

        ssize_t
        provideChunkedUpload(void *userdata, char *buffer, size_t buflen)
        {
                struct uploadData* data = (struct uploadData*)userdata;
        
                if ( buflen == 0 || data->done )
                        return 0;
                
// neon doesn't know the total size, so we have to call our progress callback by hand
                neon_progress_proc(data->worker, data->offset, data->size);
                
                int len = buflen - 10; // leave room for 6 hex digits and 2 
CRLFs
                
                if ( len > data->size - data->offset )
                        len = data->size - data->offset;
                
                // write hex chunk size
                
                int count = snprintf(buffer, buflen, "%x", len);
                
                // write CRLF
                
                buffer[count++] = '\r';
                buffer[count++] = '\n';
                
                if ( len > 0 )
                {
                        // write file data
                        int ret = read(data->fd, buffer + count, len);
                        
                        if ( ret < 0 )
                                return ret;
                        
                        data->offset += ret;
                        count += ret;
                }
                else
                        data->done = YES;
                
                // write CRLF
                
                buffer[count++] = '\r';
                buffer[count++] = '\n';
                
                return count;
        }


And the calling code, adapted from ne_put():

        // Use chunked encoding instead
        ne_request *req = ne_request_create(session, "PUT", cpath);
        
        ne_add_request_header(req, "Transfer-Encoding", "chunked");
        
        ne_lock_using_resource(req, cpath, 0);
        ne_lock_using_parent(req, cpath);
        
        struct stat st;
        
        if ( fstat(fd, &st) != 0 )
        {
                NSLog(@"couldn't stat file");
                return NO;
        }
        
        struct uploadData data = { self, st.st_size, 0, fd, NO };
ne_set_request_body_provider(req, -1, provideChunkedUpload, (void*) &data);

        err = ne_request_dispatch(req);


Chunked encoding allows for more headers after the body, so a proper implementation could add a content length or MD5 sum at the end..

Hope this is useful for someone.

-Dave
Panic, Inc.

_______________________________________________
neon mailing list
[email protected]
http://mailman.webdav.org/mailman/listinfo/neon

Reply via email to