From: Gonzalo A. <ga...@us...> - 2006-12-06 15:30:14
|
Update of /cvsroot/mod-c/ehtml/src In directory sc8-pr-cvs7.sourceforge.net:/tmp/cvs-serv16020/src Modified Files: EHTMLApplication.cpp Request.cpp Log Message: Added file upload support. Uploaded files are saved in a temporary file, which is unlinked as soon as created. Application must copy the file contents to final destination, possibly with sendfile(2). Upload progress capability is supported. A virtual method (UploadProgressCallback) is called on each chunk. Index: Request.cpp =================================================================== RCS file: /cvsroot/mod-c/ehtml/src/Request.cpp,v retrieving revision 1.24 retrieving revision 1.25 diff -C2 -d -r1.24 -r1.25 *** Request.cpp 1 Dec 2006 14:49:20 -0000 1.24 --- Request.cpp 6 Dec 2006 15:30:02 -0000 1.25 *************** *** 43,47 **** Request::Request( EHTMLApplication * App ) ! : RequestContext( App->GetRequestContext() ), Application( App ), HeaderParsed( false ), ContentLength( -1 ) { } --- 43,48 ---- Request::Request( EHTMLApplication * App ) ! : RequestContext( App->GetRequestContext() ), Application( App ), HeaderParsed( false ), ContentLength( -1 ), ! ReadSoFar(0), ContentType(CT_NONE), boundary(NULL), boundary_length(0) { } *************** *** 53,56 **** --- 54,59 ---- HeaderCookies.pop_front(); } + if (boundary) + free(boundary); } *************** *** 80,95 **** // Parse through the header PARSE_HEADER; ! char * content = 0; bool error = false; ! // Do we have the content length ! if ( ContentLength > 0 ) ! { ! ap_setup_client_block( r, REQUEST_CHUNKED_ERROR ); ! if ( ap_should_client_block( r ) ) { ! content = new char[ContentLength + 1]; ! error = ap_get_client_block( r, content, ContentLength) == 0; ! content[ContentLength] = 0; } } --- 83,119 ---- // Parse through the header PARSE_HEADER; ! char * content = NULL; bool error = false; ! if (ContentType == CT_MULTIPART_FORM_DATA) { ! Debug("file upload detected"); ! int length = 4095; ! ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); ! content = new char[length+1]; ! if (!ap_should_client_block(r)) ! break; ! long nbytes; ! size_t used = 0; ! char state = 0; // 'f': fail, 'd': done, 'p': part, 'h' header ! char* varname = NULL; ! while (0 != (nbytes = ap_get_client_block(r,content+used,length-used))) { ! content[length] = '\0'; ! used += nbytes; ! ReadSoFar += nbytes; ! Application->UploadProgressCallback(); ! ProcessUploadChunk(content, used, state, varname); ! } ! delete[] content; ! content = NULL; ! } else { ! // a simple post ! long length = ContentLength > 0 ? ContentLength : 4095; ! ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); ! if (ap_should_client_block( r )) { ! // if the client is about to send content ! content = new char[length + 1]; ! error = ap_get_client_block( r, content, length) == 0; ! content[length] = '\0'; } } *************** *** 129,132 **** --- 153,167 ---- } + int Request::GetFile( const string& name) const + { + ProfileMe(); + map<string,int>::const_iterator i = Files.find(name); + + if (i == Files.end()) + return -1; + return + i->second; + } + void Request::Error(const char* fmt, ...) const { ProfileMe(); *************** *** 197,200 **** --- 232,398 ---- } + static char* skip_eol(char* c) { + if (*c == '\r') ++c; + if (*c == '\n') ++c; + return c; + } + + static apr_status_t _close(void* p) { + int fd = (int)p; + close(fd); + return OK; + } + + /** + * Look for needle in haystack (similar to strstr(3)). + * @param haystack may be a binary data, but must be NUL terminated. + * @param hay_len length of haystack + * @param needle a NUL terminated string (must not contain NUL characters). + * @return the first location of needle inside haystack. + */ + static char* memstr(char* haystack, int hay_len, char* needle) { + while (hay_len > 0) { + char* dev = strstr(haystack, needle); + if (dev) + return dev; + size_t len = strlen(haystack); + if (len == (size_t)hay_len) + break; + hay_len -= len+1; + haystack += len+1; + } + return NULL; + } + + /** + * Process an input buffer, as far as possible. + * + * Still need to interpret Content-Transfer-Encoding part header (see rfc 2045). + * We assume that Content-Disposition is sent, which is actually not mandatory, but recommended. + * @see http://www.ietf.org/rfc/rfc1867.txt + * @see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 + */ + //#ifdef UPLOAD_DEBUG + #define DEBUG_UPLOAD(fmt,...) Debug(fmt, ## __VA_ARGS__) + //#endif + void Request::ProcessUploadChunk(char* buf, size_t& nbytes, char& state, char*& varname) { + ProfileMe(); + char *aux = buf, *eol, *eop, *eo_name; + bool is_file, again = false; + DEBUG_UPLOAD("ProcessUploadChunk: nbytes=%u boundary_length=%u", nbytes, boundary_length); + if (nbytes < boundary_length) + return; + do switch (state) { + case 0: // must skip until boundary + DEBUG_UPLOAD("ProcessUploadChunk: state: 0"); + aux = memstr(aux, (buf+nbytes)-aux, boundary); + if (!aux) { + state = 'f'; nbytes = 0; return; + } + aux += boundary_length; + DEBUG_UPLOAD("ProcessUploadChunk: aux=%s", aux); + if (!strncmp(aux,"--",2)) { + DEBUG_UPLOAD("state 0 => d"); + state = 'd'; nbytes = 0; return; + } + aux = skip_eol(aux); + state = 'h'; + DEBUG_UPLOAD("ProcessUploadChunk: state 0 => h"); + case 'h': + // we expect something like this: + // Content-Disposition: form-data; name="submit-name" + // or + // Content-Disposition: form-data; name="files"; filename="file1.txt" + // In any case, we wait for the whole line to be in a single buffer + DEBUG_UPLOAD("ProcessUploadChunk: state: h"); + DEBUG_UPLOAD("ProcessUploadChunk: aux=%s", aux); + eol = strstr(aux, "\r\n\r\n"); + if (!eol) { + DEBUG_UPLOAD("ProcessUploadChunk: waiting for full header line"); + again = false; + break; + } + *eol = '\0'; eol += 4; + DEBUG_UPLOAD("ProcessUploadChunk: aux=%s", aux); + varname = strstr(aux, " name=\""); + if (!varname) { // anonymous variable .... fail + DEBUG_UPLOAD("ProcessUploadChunk: Anonymous variable: state h => f"); + state = 'f'; nbytes = 0; return; + } + varname += 7; + eo_name = strchr(varname,'"'); + if (!eo_name) { + DEBUG_UPLOAD("ProcessUploadChunk: Error parsing variable name: state h => f"); + state = 'f'; nbytes = 0; return; + } + is_file = strstr(aux, " filename=\""); + if (strstr(aux, "Content-Transfer-Encoding: ") != NULL) { + Error("Content-Transfer-Encoding not implemented"); + state = 'f'; nbytes = 0; return; + } + *eo_name = 0; + varname = strdup(varname); + DEBUG_UPLOAD("ProcessUploadChunk: name=\"%s\"", varname); + aux = eol; + if (!is_file) { // not a file, so store as a variable + state = 'v'; + } else { + state = 'F'; + } + case 'v': + case 'F': + DEBUG_UPLOAD("ProcessUploadChunk: state = %c: aux=\"%s\"", state, aux); + if (size_t((buf+nbytes)-aux) < boundary_length) + break; + eop = memstr(aux, (buf+nbytes)-aux, boundary); + DEBUG_UPLOAD("ProcessUploadChunk: state = %c: eop=\"%s\"", state, eop); + if (!eop) + eop = buf+nbytes-boundary_length; + else { + again = true; + if (eop[-1] == '\n') --eop; + if (eop[-1] == '\r') --eop; + DEBUG_UPLOAD("ProcessUploadChunk: state = %c: eop-1=\"%s\"", state, eop-1); + } + DEBUG_UPLOAD("ProcessUploadChunk: state = %c: eop=\"%s\"", state, eop); + if (state == 'v') { // variable + DEBUG_UPLOAD("ProcessUploadChunk: \"%s\" += string(\"%s\",%d)", varname, aux, eop-aux); + Arguments[varname] += string(aux,eop-aux); + DEBUG_UPLOAD("ProcessUploadChunk: state = v: got variable \"%s\" => \"%s\"", varname, Arguments[varname].c_str()); + } else { // file + int fd = GetFile(varname); + if (fd < 0) { + char name[] = "/tmp/ehtml_upload.XXXXXX"; + fd = mkstemp(name); + if (fd < 0) { + } else { + unlink(name); + Files.insert(pair<string,int>(varname,fd)); + apr_pool_cleanup_register(RequestContext->r->pool, (void*)fd, _close, NULL); + } + } + DEBUG_UPLOAD("ProcessUploadChunk: state = F: got file (%s => %d)", varname, fd); + write(fd, aux, eop-aux); + } + if (again) { + // if (*eop == '\r') ++eop; + // if (*eop == '\n') ++eop; + state = 0; + free(varname); + varname = NULL; + } + aux = eop; + break; + case 'p': + break; + case 'd': + case 'f': + nbytes = 0; + break; + } while (again); + nbytes -= aux-buf; + memmove(buf, aux, nbytes); + } + void Request::ParseHeader() { *************** *** 253,256 **** --- 451,483 ---- } } + // Content Type? + Debug("Parsing %s: %s", tmp->key, tmp->val); + if (ContentType == CT_NONE && strncasecmp("tent-Type", _tmp+1, 9 ) == 0) { + // Seems so, check for special values + Debug("Parsing CONTENT_TYPE: %s", tmp->val); + if (strncmp(tmp->val, "multipart/form-data", 19) == 0) { + ContentType = CT_MULTIPART_FORM_DATA; + boundary = strchr(tmp->val + 19, ';'); + if (boundary) + boundary = strstr(skip_ws(boundary), "boundary="); + if (boundary) { + boundary += 9; + char* end = boundary; + while (!isspace(*end) && *end) ++end; + boundary_length = end-boundary; + char* tmpb = (char*)malloc(boundary_length+2+1); + tmpb[0] = '-'; + tmpb[1] = '-'; + strncpy(tmpb+2, boundary, boundary_length); + boundary_length += 2; + tmpb[boundary_length] = '\0'; + boundary = tmpb; + Debug("boundary=%s", boundary); + Debug("boundary_length=%d", boundary_length); + } + } else if (strncmp(tmp->val, "application/x-www-form-urlencoded", 33) == 0) { + ContentType = CT_APPLICATION_URLENCODED; + } + } } } Index: EHTMLApplication.cpp =================================================================== RCS file: /cvsroot/mod-c/ehtml/src/EHTMLApplication.cpp,v retrieving revision 1.20 retrieving revision 1.21 diff -C2 -d -r1.20 -r1.21 *** EHTMLApplication.cpp 6 Nov 2006 17:09:54 -0000 1.20 --- EHTMLApplication.cpp 6 Dec 2006 15:30:01 -0000 1.21 *************** *** 258,261 **** --- 258,266 ---- } + void EHTMLApplication::UploadProgressCallback() { + ProfileMe(); + // currently, it does nothing. + } + static const char* prefix(int level) { switch (level) { |