/** * @short This shortcut returns the given file contents. * * It sets all the compilant headers (TODO), cache and so on. * * This is the recomended way to send static files; it even can use sendfile Linux call * if suitable (TODO). * * It does no security checks, so caller must be security aware. */ onion_connection_status onion_shortcut_response_file(const char *filename, onion_request *request, onion_response *res){ int fd=open(filename,O_RDONLY|O_CLOEXEC); if (fd<0) return OCS_NOT_PROCESSED; if(O_CLOEXEC == 0) { // Good compiler know how to cut this out int flags=fcntl(fd, F_GETFD); if (flags==-1){ ONION_ERROR("Retrieving flags from file descriptor"); } flags|=FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags)==-1){ ONION_ERROR("Setting O_CLOEXEC to file descriptor"); } } struct stat st; if (stat(filename, &st)!=0){ ONION_WARNING("File does not exist: %s",filename); close(fd); return OCS_NOT_PROCESSED; } if (S_ISDIR(st.st_mode)){ close(fd); return OCS_NOT_PROCESSED; } size_t length=st.st_size; char etag[64]; onion_shortcut_etag(&st, etag); const char *range=onion_request_get_header(request, "Range"); if (range){ strncat(etag,range,sizeof(etag)-1); } onion_response_set_header(res, "Etag", etag); if (range && strncmp(range,"bytes=",6)==0){ onion_response_set_code(res, HTTP_PARTIAL_CONTENT); //ONION_DEBUG("Need just a range: %s",range); char tmp[1024]; strncpy(tmp, range+6, 1024); char *start=tmp; char *end=tmp; while (*end!='-' && *end) end++; if (*end=='-'){ *end='\0'; end++; //ONION_DEBUG("Start %s, end %s",start,end); size_t ends, starts; if (*end) ends=atol(end); else ends=length; starts=atol(start); length=ends-starts+1; lseek(fd, starts, SEEK_SET); snprintf(tmp,sizeof(tmp),"bytes %d-%d/%d",(unsigned int)starts, (unsigned int)ends, (unsigned int)st.st_size); //onion_response_set_header(res, "Accept-Ranges","bytes"); onion_response_set_header(res, "Content-Range",tmp); } } onion_response_set_length(res, length); onion_response_set_header(res, "Content-Type", onion_mime_get(filename) ); ONION_DEBUG("Mime type is %s",onion_mime_get(filename)); ONION_DEBUG0("Etag %s", etag); const char *prev_etag=onion_request_get_header(request, "If-None-Match"); if (prev_etag && (strcmp(prev_etag, etag)==0)){ ONION_DEBUG0("Not modified"); onion_response_set_length(res, 0); onion_response_set_code(res, HTTP_NOT_MODIFIED); onion_response_write_headers(res); close(fd); return OCS_PROCESSED; } onion_response_write_headers(res); if ((onion_request_get_flags(request)&OR_HEAD) == OR_HEAD){ // Just head. length=0; } if (length){ #ifdef USE_SENDFILE if (request->connection.listen_point->write==(void*)onion_http_write){ // Lets have a house party! I can use sendfile! onion_response_write(res,NULL,0); ONION_DEBUG("Using sendfile"); int r=sendfile(request->connection.fd, fd, NULL, length); ONION_DEBUG("Wrote %d, should be %d (%s)", r, length, r==length ? "ok" : "nok"); if (r!=length || r<0){ ONION_ERROR("Could not send all file (%s)", strerror(errno)); close(fd); return OCS_INTERNAL_ERROR; } res->sent_bytes+=length; res->sent_bytes_total+=length; } else #endif { // Ok, no I cant, do it as always. int r=0,w; size_t tr=0; char tmp[4046]; if (length>sizeof(tmp)){ size_t max=length-sizeof(tmp); while( tr<max ){ r=read(fd,tmp,sizeof(tmp)); tr+=r; if (r<0) break; w=onion_response_write(res, tmp, r); if (w!=r){ ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r); break; } } } if (sizeof(tmp) >= (length-tr)){ r=read(fd, tmp, length-tr); w=onion_response_write(res, tmp, r); if (w!=r){ ONION_ERROR("Wrote less than read: write %d, read %d. Quite probably closed connection.",w,r); } } } } close(fd); return OCS_PROCESSED; }
/** * @short Write the properties of a path. * * Given a path, and optionally a file at that path, writes the XML of its properties. * * If no file given, data is for that path, else it is for the path+filename. * * @param writer XML writer to write the data to * @param realpath The real path on the file server, used to check permissions and read data TODO * @param urlpath The base URL of the element, needed in the response. Composed with filename if filename!=NULL. * @param filename NULL if that element itself, else the subelement (file in path). * @param props Bitmask of the properties the user asked for. * * @return 0 is ok, 1 if could not stat file. */ int onion_webdav_write_props(xmlTextWriterPtr writer, const char *basepath, const char *realpath, const char *urlpath, const char *filename, int props){ ONION_DEBUG("Info for path '%s', urlpath '%s', file '%s', basepath '%s'", realpath, urlpath, filename, basepath); // Stat the thing itself char tmp[PATH_MAX]; if (filename) snprintf(tmp, sizeof(tmp), "%s/%s", realpath, filename); else{ if (strlen(realpath)>=sizeof(tmp)-1){ ONION_ERROR("File path too long"); return 1; } strncpy(tmp, realpath, sizeof(tmp)-1); } struct stat st; if (stat(tmp, &st)<0){ ONION_ERROR("Error on %s: %s", tmp, strerror(errno)); return 1; } while (*urlpath=='/') // No / at the begining. urlpath++; if (filename){ if (urlpath[0]==0) snprintf(tmp, sizeof(tmp), "%s/%s", basepath, filename); else snprintf(tmp, sizeof(tmp), "%s/%s/%s", basepath, urlpath, filename); } else{ if (urlpath[0]==0) snprintf(tmp, sizeof(tmp), "%s/", basepath); else{ snprintf(tmp, sizeof(tmp), "%s/%s", basepath, urlpath); } } if (S_ISDIR(st.st_mode) && urlpath[0]!=0) strncat(tmp,"/", sizeof(tmp) - 1); ONION_DEBUG0("Props for %s", tmp); xmlTextWriterStartElement(writer, BAD_CAST "D:response"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:lp1" ,BAD_CAST "DAV:"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:g0" ,BAD_CAST "DAV:"); xmlTextWriterWriteAttribute(writer, BAD_CAST "xmlns:a" ,BAD_CAST "http://apache.org/dav/props/"); xmlTextWriterWriteElement(writer, BAD_CAST "D:href", BAD_CAST tmp); /// OK xmlTextWriterStartElement(writer, BAD_CAST "D:propstat"); xmlTextWriterStartElement(writer, BAD_CAST "D:prop"); if (props&WD_RESOURCE_TYPE){ xmlTextWriterStartElement(writer, BAD_CAST "lp1:resourcetype"); if (S_ISDIR(st.st_mode)){ // no marker for other resources xmlTextWriterStartElement(writer, BAD_CAST "D:collection"); xmlTextWriterEndElement(writer); } xmlTextWriterEndElement(writer); } if (props&WD_LAST_MODIFIED){ char time[32]; onion_shortcut_date_string(st.st_mtime, time); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getlastmodified", BAD_CAST time); } if (props&WD_CREATION_DATE){ char time[32]; onion_shortcut_date_string_iso(st.st_mtime, time); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:creationdate", BAD_CAST time ); } if (props&WD_CONTENT_LENGTH && !S_ISDIR(st.st_mode)){ snprintf(tmp, sizeof(tmp), "%d", (int)st.st_size); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getcontentlength", BAD_CAST tmp); } if (props&WD_CONTENT_TYPE && S_ISDIR(st.st_mode)){ xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getcontenttype", BAD_CAST "httpd/unix-directory"); } if (props&WD_ETAG){ char etag[32]; onion_shortcut_etag(&st, etag); xmlTextWriterWriteElement(writer, BAD_CAST "lp1:getetag", BAD_CAST etag); } if (props&WD_EXECUTABLE && !S_ISDIR(st.st_mode)){ if (st.st_mode&0111) xmlTextWriterWriteElement(writer, BAD_CAST "a:executable", BAD_CAST "true"); else xmlTextWriterWriteElement(writer, BAD_CAST "a:executable", BAD_CAST "false"); } xmlTextWriterEndElement(writer); xmlTextWriterWriteElement(writer, BAD_CAST "D:status", BAD_CAST "HTTP/1.1 200 OK"); xmlTextWriterEndElement(writer); // /propstat /// NOT FOUND xmlTextWriterStartElement(writer, BAD_CAST "D:propstat"); xmlTextWriterStartElement(writer, BAD_CAST "D:prop"); if (props&WD_CONTENT_LENGTH && S_ISDIR(st.st_mode)){ snprintf(tmp, sizeof(tmp), "%d", (int)st.st_size); xmlTextWriterWriteElement(writer, BAD_CAST "g0:getcontentlength", BAD_CAST ""); } if (props&WD_CONTENT_TYPE && !S_ISDIR(st.st_mode)){ xmlTextWriterWriteElement(writer, BAD_CAST "g0:getcontenttype", BAD_CAST ""); } if (props&WD_DISPLAY_NAME){ xmlTextWriterWriteElement(writer, BAD_CAST "g0:displayname", BAD_CAST ""); } xmlTextWriterEndElement(writer); xmlTextWriterWriteElement(writer, BAD_CAST "D:status", BAD_CAST "HTTP/1.1 404 Not Found"); xmlTextWriterEndElement(writer); // /propstat xmlTextWriterEndElement(writer); return 0; }