From 3a00df2c5df06d649a3fc4ea51717cbcbc736220 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Tue, 3 Feb 2015 20:26:59 -0600 Subject: [PATCH 01/10] Document the design --- .gitignore | 2 ++ README | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.gitignore b/.gitignore index a24cbd6..fef7cde 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ cloudfuse # Ignore Eclipse files .cproject .project + +TAGS \ No newline at end of file diff --git a/README b/README index f706f59..1be513e 100644 --- a/README +++ b/README @@ -93,6 +93,36 @@ EXAMPLE: authurl=http://10.10.0.1:5000/v2.0 +HEADER SPECIFICATIONS: + + The option headers_spec option allows to specify headers to send + when a file is written. A common use is to specify the + Content-Type header. Rackspace's CloudFiles, for example, allows + 14 different headers for specifying access controls, content + expiration, and content encoding. Cloudfuse doesn't restrict what + headers can be specified, so use carefully. + + The header value is set based on matching a path against a list of + globs. Here's an example which sets the Content-Type header: + + Content-Type: /foobucket/*.html text/html, "/*/my dir/*.txt" text/plain; + + If no pattern matches, then the header is omitted. Note that + double quotes can be used to specify a pattern that has spaces. + Multiple headers can be specified, separated by semicolon (;) + + EM notes: + - Need ** for recursive matching e.g. **/*.html ? What does git use if not fnmatch()? + - Need ! for full pattern negation ? + - Disable feature in configure.ac + - Need flex or lex? Can probably use wcstok with separators ":,; " + - Need tests + - Need a way to report errors (and fail the mount) + - implement hooks + - cfs_flush triggers the PUT + - bogus content type configured in update_dir_cache + - content_type is special since it's tracked in the dir cache + BUGS/SHORTCOMINGS: * rename() doesn't work on directories (and probably never will). From 41557986d0189f32489213aecd054c75f6286a0a Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Thu, 5 Feb 2015 21:55:01 -0600 Subject: [PATCH 02/10] A header spec implementation, test suite only. 'make tests' runs a single unit test of the header spec parser/matcher. --- .gitignore | 6 +- Makefile.in | 13 ++- cloudfsapi.c | 4 +- cloudfsapi.h | 2 + cloudfuse.c | 2 +- headerspec.c | 252 ++++++++++++++++++++++++++++++++++++++++++++++ headerspec.h | 26 +++++ test/header01 | 6 ++ test/specrunner.c | 31 ++++++ 9 files changed, 336 insertions(+), 6 deletions(-) create mode 100644 headerspec.c create mode 100644 headerspec.h create mode 100755 test/header01 create mode 100644 test/specrunner.c diff --git a/.gitignore b/.gitignore index fef7cde..254d20b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,13 @@ config.status # Binary outputs cloudfuse +test/specrunner # Ignore Eclipse files .cproject .project -TAGS \ No newline at end of file +# Misc dev files +TAGS +**/*~ + diff --git a/Makefile.in b/Makefile.in index 3159c7f..5112b1b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -11,8 +11,10 @@ prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = $(DESTDIR)$(exec_prefix)/bin -SOURCES=cloudfsapi.c cloudfuse.c -HEADERS=cloudfsapi.h +SOURCES=cloudfsapi.c cloudfuse.c headerspec.c +HEADERS=cloudfsapi.h headerspec.h + +TESTS=test/header01 all: cloudfuse @@ -28,6 +30,9 @@ $(bindir): cloudfuse: $(SOURCES) $(HEADERS) $(CC) $(CFLAGS) -o cloudfuse $(SOURCES) $(LIBS) +test/specrunner: $(SOURCES) $(HEADERS) test/specrunner.c + $(CC) $(CFLAGS) -o test/specrunner test/specrunner.c headerspec.c cloudfsapi.c $(LIBS) + clean: /bin/rm -f cloudfuse @@ -52,3 +57,7 @@ Makefile: Makefile.in config.status config.status: configure ./config.status --recheck +tests: test/specrunner + for test in $(TESTS); do \ + $$test || exit 1; \ + done diff --git a/cloudfsapi.c b/cloudfsapi.c index 583964d..cf87fe0 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -84,8 +84,8 @@ static void return_connection(CURL *curl) pthread_mutex_unlock(&pool_mut); } -static void add_header(curl_slist **headers, const char *name, - const char *value) +void add_header(curl_slist **headers, const char *name, + const char *value) { char x_header[MAX_HEADER_SIZE]; snprintf(x_header, sizeof(x_header), "%s: %s", name, value); diff --git a/cloudfsapi.h b/cloudfsapi.h index b816640..499fb11 100644 --- a/cloudfsapi.h +++ b/cloudfsapi.h @@ -38,6 +38,8 @@ off_t cloudfs_file_size(int fd); void cloudfs_debug(int dbg); void cloudfs_verify_ssl(int dbg); void cloudfs_free_dir_list(dir_entry *dir_list); +void add_header(curl_slist **headers, const char *name, + const char *value); void debugf(char *fmt, ...); #endif diff --git a/cloudfuse.c b/cloudfuse.c index cd99528..c26e41b 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -16,7 +16,7 @@ #include #include "cloudfsapi.h" #include "config.h" - +#include "headerspec.h" #define OPTION_SIZE 1024 diff --git a/headerspec.c b/headerspec.c new file mode 100644 index 0000000..bd5fa8d --- /dev/null +++ b/headerspec.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include "headerspec.h" + +static int next_token(const char *spec /* in */, int *scanstart /* in/out */, + int *tokenstart /* out */, int *tokenlen /* out */) { + + const char *start = spec + *scanstart; + const char *thisc = start; + + // skip any leading whitespace + while(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r') { + thisc++; + } + + // detect end of input + if(!*thisc) return 0; + + // Handle quote + if(*thisc=='"') { + thisc++; + *tokenstart = thisc - spec; + while(1) { + if(*thisc == '"') { + *tokenlen = (thisc - spec) - *tokenstart; + *scanstart = *tokenstart + *tokenlen + 1; // trim trailing " + return 1; + } else if(*thisc == 0) { // Handle unterminated string + *tokenlen = (thisc - spec) - *tokenstart; + *scanstart = *tokenstart + *tokenlen; + return 0; + } + thisc++; + } + } else if(*thisc==':') { + *tokenstart = thisc - spec; + *tokenlen = 1; + *scanstart = *tokenstart + *tokenlen; + return 1; + } else if(*thisc==';') { + *tokenstart = thisc - spec; + *tokenlen = 1; + *scanstart = *tokenstart + *tokenlen; + return 1; + } else if(*thisc==',') { + *tokenstart = thisc - spec; + *tokenlen = 1; + *scanstart = *tokenstart + *tokenlen; + return 1; + } else if(*thisc=='!') { + *tokenstart = thisc - spec; + *tokenlen = 1; + *scanstart = *tokenstart + *tokenlen; + return 1; + } else { // An actual token + *tokenstart = thisc - spec; + + while(1) { + thisc++; + if(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r' || *thisc=='"' + || *thisc==':' || *thisc==';' || *thisc==',' || *thisc=='!') break; + } + + *tokenlen = (thisc - spec) - *tokenstart; + *scanstart = *tokenstart + *tokenlen; + + return 1; + } +} + +static enum { + EXPECT_EOF_OR_SEMI_OR_HEADERKEY, + EXPECT_COLON, + EXPECT_NEGATE_OR_MATCH, + EXPECT_MATCH, + EXPECT_HEADER_VALUE, + EXPECT_EOF_OR_COMMA_OR_SEMI +} parse_states; + +int parse_spec(const char *spec, header_spec **output) { + + int scanstart = 0; + int tokenstart, tokenlen; + + int state = EXPECT_EOF_OR_SEMI_OR_HEADERKEY; + while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) { + /* printf("Found token at %d len %d\n",tokenstart,tokenlen); */ + + char fc = *(spec+tokenstart); // first char of token + + char *headerkey, *pattern, *headervalue; + int isnegated; + header_spec *speclist_tail; + + switch(state) { + case EXPECT_EOF_OR_SEMI_OR_HEADERKEY: + if(';' == fc) continue; + if(':'==fc || '!'==fc || ','==fc) { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + headerkey = strndup(spec+tokenstart,tokenlen); + printf("Header key is %s\n",headerkey); + + // Create a new tail for the linked list. + speclist_tail = *output; + if(speclist_tail) { + while(speclist_tail->next) { + speclist_tail = speclist_tail->next; + } + speclist_tail->next = malloc(sizeof(header_spec)); + speclist_tail = speclist_tail->next; + } else { // special case for first list element + *output = malloc(sizeof(header_spec)); + speclist_tail = *output; + } + + speclist_tail->header_key = headerkey; + speclist_tail->matches = NULL; + speclist_tail->next = NULL; + + state = EXPECT_COLON; + break; + case EXPECT_COLON: + if(':'!=fc) { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + state = EXPECT_NEGATE_OR_MATCH; + break; + case EXPECT_NEGATE_OR_MATCH: + isnegated = 0; + if('!'==fc) { + printf("Match is negated\n"); + isnegated = 1; + state = EXPECT_MATCH; + } else if(':'==fc || ','==fc || ';'==fc) { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } else { + pattern = strndup(spec+tokenstart,tokenlen); + printf("Pattern is %s\n",pattern); + state = EXPECT_HEADER_VALUE; + } + break; + case EXPECT_MATCH: + if(':'==fc || '!'==fc || ','==fc || ';'==fc) { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + pattern = strndup(spec+tokenstart,tokenlen); + printf("Pattern is %s\n",pattern); + state = EXPECT_HEADER_VALUE; + break; + case EXPECT_HEADER_VALUE: + if(':'==fc || '!'==fc || ','==fc || ';'==fc) { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + headervalue = strndup(spec+tokenstart,tokenlen); + printf("Header value is %s\n",headervalue); + + match_spec *matchlist_tail; + if(speclist_tail->matches) { + matchlist_tail = speclist_tail->matches; + while(matchlist_tail->next) { + matchlist_tail = matchlist_tail->next; + } + matchlist_tail->next = malloc(sizeof(match_spec)); + matchlist_tail = matchlist_tail->next; + } else { + matchlist_tail = malloc(sizeof(match_spec)); + speclist_tail->matches = matchlist_tail; + } + + matchlist_tail->next = NULL; + matchlist_tail->pattern = pattern; + matchlist_tail->is_positive = !isnegated; + matchlist_tail->header_value = headervalue; + + state = EXPECT_EOF_OR_COMMA_OR_SEMI; + break; + case EXPECT_EOF_OR_COMMA_OR_SEMI: + if(','==fc) { + state=EXPECT_NEGATE_OR_MATCH; + } else if(';'==fc) { + state=EXPECT_EOF_OR_SEMI_OR_HEADERKEY; + } else { + printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + break; + default: + printf("In unexpected state %d with token at %d len %d\n",state,tokenstart,tokenlen); + return 0; + } + } + + if(state != EXPECT_EOF_OR_SEMI_OR_HEADERKEY && state != EXPECT_EOF_OR_COMMA_OR_SEMI) { + printf("Finished parse in unexpected state %d\n",state); + return 0; + } + + return 1; +} + +void free_spec(header_spec *spec) { + if(!spec) return; + + /* printf("Freeing spec at %p\n",spec); */ + free(spec->header_key); + + // free matches; + match_spec *onematch = spec->matches; + while(onematch) { + /* printf("Freeing match with value %s\n",onematch->header_value); */ + free(onematch->pattern); + free(onematch->header_value); + match_spec *nextmatch = onematch->next; + free(onematch); + onematch = nextmatch; + } + + header_spec *next = spec->next; + free(spec); + free_spec(next); +} + + + +int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), + struct curl_slist **headers, header_spec *spec, const char *path) { + + header_spec *onespec = spec; + while(onespec) { + + match_spec *onematch = onespec->matches; + while(onematch) { + + int result = fnmatch(onematch->pattern,path,0); + if(!result) { + add_header_func(headers,onespec->header_key,onematch->header_value); + break; + } + onematch = onematch->next; + } + + onespec = onespec->next; + } + +} diff --git a/headerspec.h b/headerspec.h new file mode 100644 index 0000000..0fc139c --- /dev/null +++ b/headerspec.h @@ -0,0 +1,26 @@ +#ifndef _HEADERSPEC_H +#define _HEADERSPEC_H + +#include + +typedef struct match_spec { + char *pattern; + int is_positive; + char *header_value; + struct match_spec *next; +} match_spec; + +typedef struct header_spec { + char *header_key; + match_spec *matches; + struct header_spec *next; +} header_spec; + +int parse_spec(const char *spec, header_spec **output); + +int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), + struct curl_slist **headers, header_spec *spec, const char *path); + +void free_spec(header_spec *spec); + +#endif // _HEADERSPEC_H diff --git a/test/header01 b/test/header01 new file mode 100755 index 0000000..ee0ac1f --- /dev/null +++ b/test/header01 @@ -0,0 +1,6 @@ +#!/bin/sh + +exec test/specrunner \ +'Content-Type: *.jpg image/jpeg, ! *.html text/plain, * text/html; Content-Disposition: * "attachment; filename=foo.txt"' \ +/bar.html + diff --git a/test/specrunner.c b/test/specrunner.c new file mode 100644 index 0000000..66a0b43 --- /dev/null +++ b/test/specrunner.c @@ -0,0 +1,31 @@ +#include +#include +#include "../headerspec.h" +#include "../cloudfsapi.h" + +int main(int argc, char **argv) { + if(argc != 3) { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + char *spec = argv[1]; + char *path = argv[2]; + + header_spec *parsed = NULL; + parse_spec(spec, &parsed); + + if(parsed) printf("parsed has been assigned\n"); + + struct curl_slist *headers = NULL; //malloc(sizeof(struct curl_slist)); + add_matching_headers(add_header,&headers,parsed,path); + + struct curl_slist *onestr = headers; + while(onestr) { + printf("Header is %s\n",onestr->data); + onestr = onestr->next; + } + + free_spec(parsed); + return 0; +} From 80d1b33e4a89cca7f392a3b253c40a7807448370 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sat, 7 Feb 2015 13:14:55 -0600 Subject: [PATCH 03/10] Working draft, unpolished --- Makefile.in | 3 +++ README | 17 ++++++----------- cloudfsapi.c | 14 +++++++++++++- cloudfsapi.h | 2 ++ cloudfuse.c | 12 +++++++++++- headerspec.c | 12 ++++++++---- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Makefile.in b/Makefile.in index 5112b1b..c397da8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -61,3 +61,6 @@ tests: test/specrunner for test in $(TESTS); do \ $$test || exit 1; \ done + +etags: + etags $$(ls *.[hc]) /usr/include/fuse/fuse.h /usr/include/fuse/fuse_opt.h /usr/include/curl/curl.h diff --git a/README b/README index 1be513e..7951544 100644 --- a/README +++ b/README @@ -112,16 +112,11 @@ HEADER SPECIFICATIONS: Multiple headers can be specified, separated by semicolon (;) EM notes: - - Need ** for recursive matching e.g. **/*.html ? What does git use if not fnmatch()? - - Need ! for full pattern negation ? - - Disable feature in configure.ac - - Need flex or lex? Can probably use wcstok with separators ":,; " - - Need tests - - Need a way to report errors (and fail the mount) - - implement hooks - - cfs_flush triggers the PUT - - bogus content type configured in update_dir_cache - - content_type is special since it's tracked in the dir cache + - TODO: + - more tests + - disable feature in configure + - factor out printf + - ensure bad parse fails the mount with good feedback BUGS/SHORTCOMINGS: @@ -148,7 +143,7 @@ AWESOME CONTRIBUTORS: * David Brownlee https://github.com/abs0 * Mike Lundy https://github.com/novas0x2a * justinb https://github.com/justinsb - + * Erik Mackdanz http://mackdanz.net Thanks, and I hope you find it useful. diff --git a/cloudfsapi.c b/cloudfsapi.c index cf87fe0..d4cfea5 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -17,6 +17,7 @@ #include #include "cloudfsapi.h" #include "config.h" +#include "headerspec.h" #define RHEL5_LIBCURL_VERSION 462597 #define RHEL5_CERTIFICATE_FILE "/etc/pki/tls/certs/ca-bundle.crt" @@ -31,6 +32,7 @@ static int curl_pool_count = 0; static int debug = 0; static int verify_ssl = 1; static int rhel5_mode = 0; +static header_spec *hspec; #ifdef HAVE_OPENSSL #include @@ -87,6 +89,7 @@ static void return_connection(CURL *curl) void add_header(curl_slist **headers, const char *name, const char *value) { + debugf("Adding the header %s: %s\n",name,value); char x_header[MAX_HEADER_SIZE]; snprintf(x_header, sizeof(x_header), "%s: %s", name, value); *headers = curl_slist_append(*headers, x_header); @@ -246,11 +249,15 @@ void cloudfs_init() int cloudfs_object_read_fp(const char *path, FILE *fp) { + debugf("In cloudfs_object_read_fp"); fflush(fp); rewind(fp); char *encoded = curl_escape(path, 0); - int response = send_request("PUT", encoded, fp, NULL, NULL); + curl_slist *headers = NULL; + add_matching_headers(add_header,&headers,hspec,path); + int response = send_request("PUT", encoded, fp, NULL, headers); curl_free(encoded); + curl_slist_free_all(headers); return (response >= 200 && response < 300); } @@ -435,6 +442,7 @@ int cloudfs_delete_object(const char *path) int cloudfs_copy_object(const char *src, const char *dst) { + debugf("In cloudfs_copy_object"); char *dst_encoded = curl_escape(dst, 0); curl_slist *headers = NULL; add_header(&headers, "X-Copy-From", src); @@ -497,6 +505,10 @@ void cloudfs_set_credentials(char *username, char *tenant, char *password, reconnect_args.use_snet = use_snet; } +void cloudfs_set_header_spec(header_spec *spec) { + hspec = spec; +} + int cloudfs_connect() { long response = -1; diff --git a/cloudfsapi.h b/cloudfsapi.h index 499fb11..c4c4b9c 100644 --- a/cloudfsapi.h +++ b/cloudfsapi.h @@ -3,6 +3,7 @@ #include #include +#include "headerspec.h" #define BUFFER_INITIAL_SIZE 4096 #define MAX_HEADER_SIZE 8192 @@ -38,6 +39,7 @@ off_t cloudfs_file_size(int fd); void cloudfs_debug(int dbg); void cloudfs_verify_ssl(int dbg); void cloudfs_free_dir_list(dir_entry *dir_list); +void cloudfs_set_header_spec(header_spec *spec); void add_header(curl_slist **headers, const char *name, const char *value); diff --git a/cloudfuse.c b/cloudfuse.c index c26e41b..49284e3 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -433,6 +433,7 @@ static struct options { char region[OPTION_SIZE]; char use_snet[OPTION_SIZE]; char verify_ssl[OPTION_SIZE]; + char header_spec[OPTION_SIZE*10]; } options = { .username = "", .password = "", @@ -442,6 +443,7 @@ static struct options { .region = "", .use_snet = "false", .verify_ssl = "true", + .header_spec = "" }; int parse_option(void *data, const char *arg, int key, struct fuse_args *outargs) @@ -454,7 +456,8 @@ int parse_option(void *data, const char *arg, int key, struct fuse_args *outargs sscanf(arg, " authurl = %[^\r\n ]", options.authurl) || sscanf(arg, " region = %[^\r\n ]", options.region) || sscanf(arg, " use_snet = %[^\r\n ]", options.use_snet) || - sscanf(arg, " verify_ssl = %[^\r\n ]", options.verify_ssl)) + sscanf(arg, " verify_ssl = %[^\r\n ]", options.verify_ssl) || + sscanf(arg, " header_spec = %[^\r\n]", options.header_spec)) // Note spaces permitted return 0; if (!strcmp(arg, "-f") || !strcmp(arg, "-d") || !strcmp(arg, "debug")) cloudfs_debug(1); @@ -494,12 +497,17 @@ int main(int argc, char **argv) fprintf(stderr, " use_snet=[True to use Rackspace ServiceNet for connections]\n"); fprintf(stderr, " cache_timeout=[Seconds for directory caching, default 600]\n"); fprintf(stderr, " verify_ssl=[False to disable SSL cert verification]\n"); + fprintf(stderr, " header_spec=[Match specification for writing extra headers, see README]\n"); return 1; } cloudfs_init(); + header_spec *parsed = NULL; + parse_spec(options.header_spec, &parsed); + cloudfs_set_header_spec(parsed); + cloudfs_verify_ssl(!strcasecmp(options.verify_ssl, "true")); cloudfs_set_credentials(options.username, options.tenant, options.password, @@ -541,5 +549,7 @@ int main(int argc, char **argv) pthread_mutex_init(&dmut, NULL); return fuse_main(args.argc, args.argv, &cfs_oper, &options); + + free_spec(parsed); } diff --git a/headerspec.c b/headerspec.c index bd5fa8d..2adccd4 100644 --- a/headerspec.c +++ b/headerspec.c @@ -59,7 +59,7 @@ static int next_token(const char *spec /* in */, int *scanstart /* in/out */, while(1) { thisc++; if(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r' || *thisc=='"' - || *thisc==':' || *thisc==';' || *thisc==',' || *thisc=='!') break; + || *thisc==':' || *thisc==';' || *thisc==',' || *thisc=='!' || *thisc==0) break; } *tokenlen = (thisc - spec) - *tokenstart; @@ -80,12 +80,14 @@ static enum { int parse_spec(const char *spec, header_spec **output) { + printf("Entire spec is %s\n",spec); + int scanstart = 0; int tokenstart, tokenlen; int state = EXPECT_EOF_OR_SEMI_OR_HEADERKEY; while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) { - /* printf("Found token at %d len %d\n",tokenstart,tokenlen); */ + printf("Found token at %d len %d\n",tokenstart,tokenlen); char fc = *(spec+tokenstart); // first char of token @@ -116,6 +118,7 @@ int parse_spec(const char *spec, header_spec **output) { speclist_tail = *output; } + printf("Allocated speclist\n"); speclist_tail->header_key = headerkey; speclist_tail->matches = NULL; speclist_tail->next = NULL; @@ -202,6 +205,7 @@ int parse_spec(const char *spec, header_spec **output) { return 0; } + printf("Parse completed successfully\n"); return 1; } @@ -227,18 +231,18 @@ void free_spec(header_spec *spec) { free_spec(next); } - - int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), struct curl_slist **headers, header_spec *spec, const char *path) { header_spec *onespec = spec; while(onespec) { + printf("Testing one spec\n"); match_spec *onematch = onespec->matches; while(onematch) { int result = fnmatch(onematch->pattern,path,0); + printf("Testing one match, path being %s, result was %d\n",path,result); if(!result) { add_header_func(headers,onespec->header_key,onematch->header_value); break; From 83cdd13ab93d435eb14692ddf4dda2511e031be1 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sat, 7 Feb 2015 15:33:12 -0600 Subject: [PATCH 04/10] Factor out printf --- README | 7 +++++-- headerspec.c | 43 ++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/README b/README index 7951544..50ab99e 100644 --- a/README +++ b/README @@ -100,7 +100,7 @@ HEADER SPECIFICATIONS: Content-Type header. Rackspace's CloudFiles, for example, allows 14 different headers for specifying access controls, content expiration, and content encoding. Cloudfuse doesn't restrict what - headers can be specified, so use carefully. + headers can be specified, so use this feature carefully. The header value is set based on matching a path against a list of globs. Here's an example which sets the Content-Type header: @@ -111,11 +111,14 @@ HEADER SPECIFICATIONS: double quotes can be used to specify a pattern that has spaces. Multiple headers can be specified, separated by semicolon (;) + The path looks like an absolute path beginning at the mount point. + For example, a file "index.html" in bucket "foo" will result in + the string "/foo/index.html" being matched against the glob pattern. + EM notes: - TODO: - more tests - disable feature in configure - - factor out printf - ensure bad parse fails the mount with good feedback BUGS/SHORTCOMINGS: diff --git a/headerspec.c b/headerspec.c index 2adccd4..445d301 100644 --- a/headerspec.c +++ b/headerspec.c @@ -2,6 +2,7 @@ #include #include #include "headerspec.h" +#include "cloudfsapi.h" static int next_token(const char *spec /* in */, int *scanstart /* in/out */, int *tokenstart /* out */, int *tokenlen /* out */) { @@ -80,14 +81,14 @@ static enum { int parse_spec(const char *spec, header_spec **output) { - printf("Entire spec is %s\n",spec); + debugf("Entire spec is %s\n",spec); int scanstart = 0; int tokenstart, tokenlen; int state = EXPECT_EOF_OR_SEMI_OR_HEADERKEY; while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) { - printf("Found token at %d len %d\n",tokenstart,tokenlen); + debugf("Found token at %d len %d\n",tokenstart,tokenlen); char fc = *(spec+tokenstart); // first char of token @@ -99,11 +100,11 @@ int parse_spec(const char *spec, header_spec **output) { case EXPECT_EOF_OR_SEMI_OR_HEADERKEY: if(';' == fc) continue; if(':'==fc || '!'==fc || ','==fc) { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } headerkey = strndup(spec+tokenstart,tokenlen); - printf("Header key is %s\n",headerkey); + debugf("Header key is %s\n",headerkey); // Create a new tail for the linked list. speclist_tail = *output; @@ -118,7 +119,7 @@ int parse_spec(const char *spec, header_spec **output) { speclist_tail = *output; } - printf("Allocated speclist\n"); + debugf("Allocated speclist\n"); speclist_tail->header_key = headerkey; speclist_tail->matches = NULL; speclist_tail->next = NULL; @@ -127,7 +128,7 @@ int parse_spec(const char *spec, header_spec **output) { break; case EXPECT_COLON: if(':'!=fc) { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } state = EXPECT_NEGATE_OR_MATCH; @@ -135,34 +136,34 @@ int parse_spec(const char *spec, header_spec **output) { case EXPECT_NEGATE_OR_MATCH: isnegated = 0; if('!'==fc) { - printf("Match is negated\n"); + debugf("Match is negated\n"); isnegated = 1; state = EXPECT_MATCH; } else if(':'==fc || ','==fc || ';'==fc) { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } else { pattern = strndup(spec+tokenstart,tokenlen); - printf("Pattern is %s\n",pattern); + debugf("Pattern is %s\n",pattern); state = EXPECT_HEADER_VALUE; } break; case EXPECT_MATCH: if(':'==fc || '!'==fc || ','==fc || ';'==fc) { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } pattern = strndup(spec+tokenstart,tokenlen); - printf("Pattern is %s\n",pattern); + debugf("Pattern is %s\n",pattern); state = EXPECT_HEADER_VALUE; break; case EXPECT_HEADER_VALUE: if(':'==fc || '!'==fc || ','==fc || ';'==fc) { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } headervalue = strndup(spec+tokenstart,tokenlen); - printf("Header value is %s\n",headervalue); + debugf("Header value is %s\n",headervalue); match_spec *matchlist_tail; if(speclist_tail->matches) { @@ -190,35 +191,35 @@ int parse_spec(const char *spec, header_spec **output) { } else if(';'==fc) { state=EXPECT_EOF_OR_SEMI_OR_HEADERKEY; } else { - printf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } break; default: - printf("In unexpected state %d with token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In unexpected state %d with token at %d len %d\n",state,tokenstart,tokenlen); return 0; } } if(state != EXPECT_EOF_OR_SEMI_OR_HEADERKEY && state != EXPECT_EOF_OR_COMMA_OR_SEMI) { - printf("Finished parse in unexpected state %d\n",state); + debugf("Finished parse in unexpected state %d\n",state); return 0; } - printf("Parse completed successfully\n"); + debugf("Parse completed successfully\n"); return 1; } void free_spec(header_spec *spec) { if(!spec) return; - /* printf("Freeing spec at %p\n",spec); */ + /* debugf("Freeing spec at %p\n",spec); */ free(spec->header_key); // free matches; match_spec *onematch = spec->matches; while(onematch) { - /* printf("Freeing match with value %s\n",onematch->header_value); */ + /* debugf("Freeing match with value %s\n",onematch->header_value); */ free(onematch->pattern); free(onematch->header_value); match_spec *nextmatch = onematch->next; @@ -237,12 +238,12 @@ int add_matching_headers(void (add_header_func)(struct curl_slist **headers, con header_spec *onespec = spec; while(onespec) { - printf("Testing one spec\n"); + debugf("Testing one spec\n"); match_spec *onematch = onespec->matches; while(onematch) { int result = fnmatch(onematch->pattern,path,0); - printf("Testing one match, path being %s, result was %d\n",path,result); + debugf("Testing one match, path being %s, result was %d\n",path,result); if(!result) { add_header_func(headers,onespec->header_key,onematch->header_value); break; From 51c639b7cc024c331c1c0c80706b21e7fef481d3 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sat, 7 Feb 2015 22:56:37 -0600 Subject: [PATCH 05/10] Increase test coverage, fix negation logic Also ensure that a bad parse fails the mount. --- Makefile.in | 5 ++++- README | 3 +-- cloudfuse.c | 6 +++++- headerspec.c | 5 ++++- test/header01 | 16 +++++++++++++--- test/header02 | 16 ++++++++++++++++ test/header03 | 16 ++++++++++++++++ test/header04 | 11 +++++++++++ test/specrunner.c | 9 +++++---- 9 files changed, 75 insertions(+), 12 deletions(-) create mode 100755 test/header02 create mode 100755 test/header03 create mode 100755 test/header04 diff --git a/Makefile.in b/Makefile.in index c397da8..f017dee 100644 --- a/Makefile.in +++ b/Makefile.in @@ -14,7 +14,10 @@ bindir = $(DESTDIR)$(exec_prefix)/bin SOURCES=cloudfsapi.c cloudfuse.c headerspec.c HEADERS=cloudfsapi.h headerspec.h -TESTS=test/header01 +TESTS=test/header01 \ + test/header02 \ + test/header03 \ + test/header04 all: cloudfuse diff --git a/README b/README index 50ab99e..c083e54 100644 --- a/README +++ b/README @@ -117,9 +117,8 @@ HEADER SPECIFICATIONS: EM notes: - TODO: - - more tests - disable feature in configure - - ensure bad parse fails the mount with good feedback + - GNU brace style BUGS/SHORTCOMINGS: diff --git a/cloudfuse.c b/cloudfuse.c index 49284e3..8606ff4 100644 --- a/cloudfuse.c +++ b/cloudfuse.c @@ -505,7 +505,11 @@ int main(int argc, char **argv) cloudfs_init(); header_spec *parsed = NULL; - parse_spec(options.header_spec, &parsed); + if(!parse_spec(options.header_spec, &parsed)) + { + fprintf(stderr, "Could not parse header spec\n"); + return 1; + } cloudfs_set_header_spec(parsed); cloudfs_verify_ssl(!strcasecmp(options.verify_ssl, "true")); diff --git a/headerspec.c b/headerspec.c index 445d301..67f21f8 100644 --- a/headerspec.c +++ b/headerspec.c @@ -244,9 +244,12 @@ int add_matching_headers(void (add_header_func)(struct curl_slist **headers, con int result = fnmatch(onematch->pattern,path,0); debugf("Testing one match, path being %s, result was %d\n",path,result); - if(!result) { + if((result==0 && onematch->is_positive) || (result==FNM_NOMATCH && !onematch->is_positive)) { add_header_func(headers,onespec->header_key,onematch->header_value); break; + } else if(result!=0 && result != FNM_NOMATCH) { + debugf("fnmatch error\n"); + break; } onematch = onematch->next; } diff --git a/test/header01 b/test/header01 index ee0ac1f..80b6112 100755 --- a/test/header01 +++ b/test/header01 @@ -1,6 +1,16 @@ #!/bin/sh -exec test/specrunner \ -'Content-Type: *.jpg image/jpeg, ! *.html text/plain, * text/html; Content-Disposition: * "attachment; filename=foo.txt"' \ -/bar.html +set -e +set -x + +result=$(mktemp) + +test/specrunner \ +'Content-Type: *.jpg image/jpeg, ! *.txt text/html; Content-Disposition: * "attachment; filename=foo.txt"' \ +/bar.html >$result + +grep "Content-Type: text/html" $result +grep "Content-Disposition: attachment; filename=foo.txt" $result + +rm $result diff --git a/test/header02 b/test/header02 new file mode 100755 index 0000000..68d8c24 --- /dev/null +++ b/test/header02 @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e +set -x + +result=$(mktemp) + +test/specrunner \ +'Content-Type: *.css text/css, /blog/code/*/repository/raw/* text/plain, /blog/code/* text/html' \ +/blog/code/foo >$result + +grep "Content-Type: text/html" $result + +rm $result + + diff --git a/test/header03 b/test/header03 new file mode 100755 index 0000000..80c7c84 --- /dev/null +++ b/test/header03 @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e +set -x + +result=$(mktemp) + +test/specrunner \ +'Content-Type: *.css text/css, /blog/code/*/repository/raw/* text/plain, /blog/code/* text/html; Content-Disposition: * bar' \ +/foo >$result + +grep "Content-Disposition: bar" $result + +rm $result + + diff --git a/test/header04 b/test/header04 new file mode 100755 index 0000000..1cd6f8e --- /dev/null +++ b/test/header04 @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e +set -x + +# Ensure bad parse detected +if test/specrunner 'Content-Disposition: bar' /foo; then + exit 1 +fi + + diff --git a/test/specrunner.c b/test/specrunner.c index 66a0b43..e401a70 100644 --- a/test/specrunner.c +++ b/test/specrunner.c @@ -13,16 +13,17 @@ int main(int argc, char **argv) { char *path = argv[2]; header_spec *parsed = NULL; - parse_spec(spec, &parsed); - - if(parsed) printf("parsed has been assigned\n"); + if(!parse_spec(spec, &parsed)) { + printf("Bad parse\n"); + return 1; + } struct curl_slist *headers = NULL; //malloc(sizeof(struct curl_slist)); add_matching_headers(add_header,&headers,parsed,path); struct curl_slist *onestr = headers; while(onestr) { - printf("Header is %s\n",onestr->data); + printf("%s\n",onestr->data); onestr = onestr->next; } From 2e47b5ee18ffb4eaab7f71ec2b3974d732faf093 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sun, 8 Feb 2015 10:51:45 -0600 Subject: [PATCH 06/10] Respect brace style --- cloudfsapi.c | 5 +- headerspec.c | 139 +++++++++++++++++++++++++++++++--------------- headerspec.h | 6 +- test/specrunner.c | 14 +++-- 4 files changed, 109 insertions(+), 55 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index d4cfea5..0e4b86c 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -249,7 +249,6 @@ void cloudfs_init() int cloudfs_object_read_fp(const char *path, FILE *fp) { - debugf("In cloudfs_object_read_fp"); fflush(fp); rewind(fp); char *encoded = curl_escape(path, 0); @@ -442,7 +441,6 @@ int cloudfs_delete_object(const char *path) int cloudfs_copy_object(const char *src, const char *dst) { - debugf("In cloudfs_copy_object"); char *dst_encoded = curl_escape(dst, 0); curl_slist *headers = NULL; add_header(&headers, "X-Copy-From", src); @@ -505,7 +503,8 @@ void cloudfs_set_credentials(char *username, char *tenant, char *password, reconnect_args.use_snet = use_snet; } -void cloudfs_set_header_spec(header_spec *spec) { +void cloudfs_set_header_spec(header_spec *spec) +{ hspec = spec; } diff --git a/headerspec.c b/headerspec.c index 67f21f8..d7a774f 100644 --- a/headerspec.c +++ b/headerspec.c @@ -5,13 +5,15 @@ #include "cloudfsapi.h" static int next_token(const char *spec /* in */, int *scanstart /* in/out */, - int *tokenstart /* out */, int *tokenlen /* out */) { + int *tokenstart /* out */, int *tokenlen /* out */) +{ const char *start = spec + *scanstart; const char *thisc = start; // skip any leading whitespace - while(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r') { + while(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r') + { thisc++; } @@ -19,50 +21,66 @@ static int next_token(const char *spec /* in */, int *scanstart /* in/out */, if(!*thisc) return 0; // Handle quote - if(*thisc=='"') { + if(*thisc=='"') + { thisc++; *tokenstart = thisc - spec; - while(1) { - if(*thisc == '"') { + while(1) + { + if(*thisc == '"') + { *tokenlen = (thisc - spec) - *tokenstart; *scanstart = *tokenstart + *tokenlen + 1; // trim trailing " return 1; - } else if(*thisc == 0) { // Handle unterminated string + } + else if(*thisc == 0) // Handle unterminated string + { *tokenlen = (thisc - spec) - *tokenstart; *scanstart = *tokenstart + *tokenlen; return 0; } thisc++; } - } else if(*thisc==':') { + } + else if(*thisc==':') + { *tokenstart = thisc - spec; *tokenlen = 1; *scanstart = *tokenstart + *tokenlen; return 1; - } else if(*thisc==';') { + } + else if(*thisc==';') + { *tokenstart = thisc - spec; *tokenlen = 1; *scanstart = *tokenstart + *tokenlen; return 1; - } else if(*thisc==',') { + } + else if(*thisc==',') + { *tokenstart = thisc - spec; *tokenlen = 1; *scanstart = *tokenstart + *tokenlen; return 1; - } else if(*thisc=='!') { + } + else if(*thisc=='!') + { *tokenstart = thisc - spec; *tokenlen = 1; *scanstart = *tokenstart + *tokenlen; return 1; - } else { // An actual token + } + else // An actual token + { *tokenstart = thisc - spec; - while(1) { + while(1) + { thisc++; if(*thisc==' ' || *thisc=='\t' || *thisc=='\n' || *thisc=='\r' || *thisc=='"' || *thisc==':' || *thisc==';' || *thisc==',' || *thisc=='!' || *thisc==0) break; } - + *tokenlen = (thisc - spec) - *tokenstart; *scanstart = *tokenstart + *tokenlen; @@ -70,7 +88,8 @@ static int next_token(const char *spec /* in */, int *scanstart /* in/out */, } } -static enum { +static enum +{ EXPECT_EOF_OR_SEMI_OR_HEADERKEY, EXPECT_COLON, EXPECT_NEGATE_OR_MATCH, @@ -79,7 +98,8 @@ static enum { EXPECT_EOF_OR_COMMA_OR_SEMI } parse_states; -int parse_spec(const char *spec, header_spec **output) { +int parse_spec(const char *spec, header_spec **output) +{ debugf("Entire spec is %s\n",spec); @@ -87,7 +107,8 @@ int parse_spec(const char *spec, header_spec **output) { int tokenstart, tokenlen; int state = EXPECT_EOF_OR_SEMI_OR_HEADERKEY; - while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) { + while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) + { debugf("Found token at %d len %d\n",tokenstart,tokenlen); char fc = *(spec+tokenstart); // first char of token @@ -96,10 +117,12 @@ int parse_spec(const char *spec, header_spec **output) { int isnegated; header_spec *speclist_tail; - switch(state) { + switch(state) + { case EXPECT_EOF_OR_SEMI_OR_HEADERKEY: if(';' == fc) continue; - if(':'==fc || '!'==fc || ','==fc) { + if(':'==fc || '!'==fc || ','==fc) + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } @@ -108,13 +131,17 @@ int parse_spec(const char *spec, header_spec **output) { // Create a new tail for the linked list. speclist_tail = *output; - if(speclist_tail) { - while(speclist_tail->next) { + if(speclist_tail) + { + while(speclist_tail->next) + { speclist_tail = speclist_tail->next; } speclist_tail->next = malloc(sizeof(header_spec)); speclist_tail = speclist_tail->next; - } else { // special case for first list element + } + else // special case for first list element + { *output = malloc(sizeof(header_spec)); speclist_tail = *output; } @@ -127,7 +154,8 @@ int parse_spec(const char *spec, header_spec **output) { state = EXPECT_COLON; break; case EXPECT_COLON: - if(':'!=fc) { + if(':'!=fc) + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } @@ -135,21 +163,27 @@ int parse_spec(const char *spec, header_spec **output) { break; case EXPECT_NEGATE_OR_MATCH: isnegated = 0; - if('!'==fc) { + if('!'==fc) + { debugf("Match is negated\n"); isnegated = 1; state = EXPECT_MATCH; - } else if(':'==fc || ','==fc || ';'==fc) { + } + else if(':'==fc || ','==fc || ';'==fc) + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; - } else { + } + else + { pattern = strndup(spec+tokenstart,tokenlen); debugf("Pattern is %s\n",pattern); state = EXPECT_HEADER_VALUE; } break; case EXPECT_MATCH: - if(':'==fc || '!'==fc || ','==fc || ';'==fc) { + if(':'==fc || '!'==fc || ','==fc || ';'==fc) + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } @@ -158,7 +192,8 @@ int parse_spec(const char *spec, header_spec **output) { state = EXPECT_HEADER_VALUE; break; case EXPECT_HEADER_VALUE: - if(':'==fc || '!'==fc || ','==fc || ';'==fc) { + if(':'==fc || '!'==fc || ','==fc || ';'==fc) + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } @@ -166,14 +201,18 @@ int parse_spec(const char *spec, header_spec **output) { debugf("Header value is %s\n",headervalue); match_spec *matchlist_tail; - if(speclist_tail->matches) { + if(speclist_tail->matches) + { matchlist_tail = speclist_tail->matches; - while(matchlist_tail->next) { + while(matchlist_tail->next) + { matchlist_tail = matchlist_tail->next; } matchlist_tail->next = malloc(sizeof(match_spec)); matchlist_tail = matchlist_tail->next; - } else { + } + else + { matchlist_tail = malloc(sizeof(match_spec)); speclist_tail->matches = matchlist_tail; } @@ -186,11 +225,16 @@ int parse_spec(const char *spec, header_spec **output) { state = EXPECT_EOF_OR_COMMA_OR_SEMI; break; case EXPECT_EOF_OR_COMMA_OR_SEMI: - if(','==fc) { + if(','==fc) + { state=EXPECT_NEGATE_OR_MATCH; - } else if(';'==fc) { + } + else if(';'==fc) + { state=EXPECT_EOF_OR_SEMI_OR_HEADERKEY; - } else { + } + else + { debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); return 0; } @@ -201,7 +245,8 @@ int parse_spec(const char *spec, header_spec **output) { } } - if(state != EXPECT_EOF_OR_SEMI_OR_HEADERKEY && state != EXPECT_EOF_OR_COMMA_OR_SEMI) { + if(state != EXPECT_EOF_OR_SEMI_OR_HEADERKEY && state != EXPECT_EOF_OR_COMMA_OR_SEMI) + { debugf("Finished parse in unexpected state %d\n",state); return 0; } @@ -210,7 +255,8 @@ int parse_spec(const char *spec, header_spec **output) { return 1; } -void free_spec(header_spec *spec) { +void free_spec(header_spec *spec) +{ if(!spec) return; /* debugf("Freeing spec at %p\n",spec); */ @@ -218,7 +264,8 @@ void free_spec(header_spec *spec) { // free matches; match_spec *onematch = spec->matches; - while(onematch) { + while(onematch) + { /* debugf("Freeing match with value %s\n",onematch->header_value); */ free(onematch->pattern); free(onematch->header_value); @@ -233,28 +280,30 @@ void free_spec(header_spec *spec) { } int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), - struct curl_slist **headers, header_spec *spec, const char *path) { + struct curl_slist **headers, header_spec *spec, const char *path) +{ header_spec *onespec = spec; - while(onespec) { - + while(onespec) + { debugf("Testing one spec\n"); match_spec *onematch = onespec->matches; - while(onematch) { - + while(onematch) + { int result = fnmatch(onematch->pattern,path,0); debugf("Testing one match, path being %s, result was %d\n",path,result); - if((result==0 && onematch->is_positive) || (result==FNM_NOMATCH && !onematch->is_positive)) { + if((result==0 && onematch->is_positive) || (result==FNM_NOMATCH && !onematch->is_positive)) + { add_header_func(headers,onespec->header_key,onematch->header_value); break; - } else if(result!=0 && result != FNM_NOMATCH) { + } + else if(result!=0 && result != FNM_NOMATCH) + { debugf("fnmatch error\n"); break; } onematch = onematch->next; } - onespec = onespec->next; } - } diff --git a/headerspec.h b/headerspec.h index 0fc139c..71ed97d 100644 --- a/headerspec.h +++ b/headerspec.h @@ -3,14 +3,16 @@ #include -typedef struct match_spec { +typedef struct match_spec +{ char *pattern; int is_positive; char *header_value; struct match_spec *next; } match_spec; -typedef struct header_spec { +typedef struct header_spec +{ char *header_key; match_spec *matches; struct header_spec *next; diff --git a/test/specrunner.c b/test/specrunner.c index e401a70..e122459 100644 --- a/test/specrunner.c +++ b/test/specrunner.c @@ -3,8 +3,10 @@ #include "../headerspec.h" #include "../cloudfsapi.h" -int main(int argc, char **argv) { - if(argc != 3) { +int main(int argc, char **argv) +{ + if(argc != 3) + { printf("Usage: %s \n", argv[0]); exit(1); } @@ -13,16 +15,18 @@ int main(int argc, char **argv) { char *path = argv[2]; header_spec *parsed = NULL; - if(!parse_spec(spec, &parsed)) { + if(!parse_spec(spec, &parsed)) + { printf("Bad parse\n"); return 1; } - struct curl_slist *headers = NULL; //malloc(sizeof(struct curl_slist)); + struct curl_slist *headers = NULL; add_matching_headers(add_header,&headers,parsed,path); struct curl_slist *onestr = headers; - while(onestr) { + while(onestr) + { printf("%s\n",onestr->data); onestr = onestr->next; } From 5ee9efd98bf16d12ace3e22826cbcb9ab013f20e Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sun, 8 Feb 2015 10:56:50 -0600 Subject: [PATCH 07/10] Remove needless function pointer --- cloudfsapi.c | 2 +- headerspec.c | 5 ++--- headerspec.h | 3 +-- test/specrunner.c | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 0e4b86c..5e0941e 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -253,7 +253,7 @@ int cloudfs_object_read_fp(const char *path, FILE *fp) rewind(fp); char *encoded = curl_escape(path, 0); curl_slist *headers = NULL; - add_matching_headers(add_header,&headers,hspec,path); + add_matching_headers(&headers,hspec,path); int response = send_request("PUT", encoded, fp, NULL, headers); curl_free(encoded); curl_slist_free_all(headers); diff --git a/headerspec.c b/headerspec.c index d7a774f..f7ec376 100644 --- a/headerspec.c +++ b/headerspec.c @@ -279,8 +279,7 @@ void free_spec(header_spec *spec) free_spec(next); } -int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), - struct curl_slist **headers, header_spec *spec, const char *path) +int add_matching_headers(struct curl_slist **headers, header_spec *spec, const char *path) { header_spec *onespec = spec; @@ -294,7 +293,7 @@ int add_matching_headers(void (add_header_func)(struct curl_slist **headers, con debugf("Testing one match, path being %s, result was %d\n",path,result); if((result==0 && onematch->is_positive) || (result==FNM_NOMATCH && !onematch->is_positive)) { - add_header_func(headers,onespec->header_key,onematch->header_value); + add_header(headers,onespec->header_key,onematch->header_value); break; } else if(result!=0 && result != FNM_NOMATCH) diff --git a/headerspec.h b/headerspec.h index 71ed97d..722e7e8 100644 --- a/headerspec.h +++ b/headerspec.h @@ -20,8 +20,7 @@ typedef struct header_spec int parse_spec(const char *spec, header_spec **output); -int add_matching_headers(void (add_header_func)(struct curl_slist **headers, const char *name, const char *value), - struct curl_slist **headers, header_spec *spec, const char *path); +int add_matching_headers(struct curl_slist **headers, header_spec *spec, const char *path); void free_spec(header_spec *spec); diff --git a/test/specrunner.c b/test/specrunner.c index e122459..55c07dc 100644 --- a/test/specrunner.c +++ b/test/specrunner.c @@ -22,7 +22,7 @@ int main(int argc, char **argv) } struct curl_slist *headers = NULL; - add_matching_headers(add_header,&headers,parsed,path); + add_matching_headers(&headers,parsed,path); struct curl_slist *onestr = headers; while(onestr) From 1f187d0b894ddcfd562caf049d5deba1b5f72382 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sun, 8 Feb 2015 11:08:11 -0600 Subject: [PATCH 08/10] More info in README --- README | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README b/README index c083e54..02b5f44 100644 --- a/README +++ b/README @@ -100,25 +100,30 @@ HEADER SPECIFICATIONS: Content-Type header. Rackspace's CloudFiles, for example, allows 14 different headers for specifying access controls, content expiration, and content encoding. Cloudfuse doesn't restrict what - headers can be specified, so use this feature carefully. + headers can be specified. The header value is set based on matching a path against a list of - globs. Here's an example which sets the Content-Type header: + globs. The syntax: + + HeaderKey: GlobMatch HeaderValue [, GlobMatch HeaderValue...] [; HeaderKey...] + + Here's an example which sets the Content-Type header: Content-Type: /foobucket/*.html text/html, "/*/my dir/*.txt" text/plain; If no pattern matches, then the header is omitted. Note that double quotes can be used to specify a pattern that has spaces. Multiple headers can be specified, separated by semicolon (;) + More examples are available in the test directory. + + See 'man 3 fnmatch' for accepted glob patterns. The path looks like an absolute path beginning at the mount point. For example, a file "index.html" in bucket "foo" will result in - the string "/foo/index.html" being matched against the glob pattern. + the string "/foo/index.html" being matched against the glob + pattern (even if the absolute path on your system is actually + /var/www/foo/index.html). - EM notes: - - TODO: - - disable feature in configure - - GNU brace style BUGS/SHORTCOMINGS: From d892c1954f25a961c865ec04fe634acba542f470 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sun, 8 Feb 2015 13:20:19 -0600 Subject: [PATCH 09/10] Remove duplicate newline --- cloudfsapi.c | 2 +- headerspec.c | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cloudfsapi.c b/cloudfsapi.c index 5e0941e..77fb3e8 100644 --- a/cloudfsapi.c +++ b/cloudfsapi.c @@ -89,7 +89,7 @@ static void return_connection(CURL *curl) void add_header(curl_slist **headers, const char *name, const char *value) { - debugf("Adding the header %s: %s\n",name,value); + debugf("Adding the header %s: %s",name,value); char x_header[MAX_HEADER_SIZE]; snprintf(x_header, sizeof(x_header), "%s: %s", name, value); *headers = curl_slist_append(*headers, x_header); diff --git a/headerspec.c b/headerspec.c index f7ec376..0081503 100644 --- a/headerspec.c +++ b/headerspec.c @@ -101,7 +101,7 @@ static enum int parse_spec(const char *spec, header_spec **output) { - debugf("Entire spec is %s\n",spec); + debugf("Entire spec is %s",spec); int scanstart = 0; int tokenstart, tokenlen; @@ -109,7 +109,7 @@ int parse_spec(const char *spec, header_spec **output) int state = EXPECT_EOF_OR_SEMI_OR_HEADERKEY; while(next_token(spec,&scanstart,&tokenstart,&tokenlen)) { - debugf("Found token at %d len %d\n",tokenstart,tokenlen); + debugf("Found token at %d len %d",tokenstart,tokenlen); char fc = *(spec+tokenstart); // first char of token @@ -123,11 +123,11 @@ int parse_spec(const char *spec, header_spec **output) if(';' == fc) continue; if(':'==fc || '!'==fc || ','==fc) { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } headerkey = strndup(spec+tokenstart,tokenlen); - debugf("Header key is %s\n",headerkey); + debugf("Header key is %s",headerkey); // Create a new tail for the linked list. speclist_tail = *output; @@ -146,7 +146,7 @@ int parse_spec(const char *spec, header_spec **output) speclist_tail = *output; } - debugf("Allocated speclist\n"); + debugf("Allocated speclist"); speclist_tail->header_key = headerkey; speclist_tail->matches = NULL; speclist_tail->next = NULL; @@ -156,7 +156,7 @@ int parse_spec(const char *spec, header_spec **output) case EXPECT_COLON: if(':'!=fc) { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } state = EXPECT_NEGATE_OR_MATCH; @@ -165,40 +165,40 @@ int parse_spec(const char *spec, header_spec **output) isnegated = 0; if('!'==fc) { - debugf("Match is negated\n"); + debugf("Match is negated"); isnegated = 1; state = EXPECT_MATCH; } else if(':'==fc || ','==fc || ';'==fc) { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } else { pattern = strndup(spec+tokenstart,tokenlen); - debugf("Pattern is %s\n",pattern); + debugf("Pattern is %s",pattern); state = EXPECT_HEADER_VALUE; } break; case EXPECT_MATCH: if(':'==fc || '!'==fc || ','==fc || ';'==fc) { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } pattern = strndup(spec+tokenstart,tokenlen); - debugf("Pattern is %s\n",pattern); + debugf("Pattern is %s",pattern); state = EXPECT_HEADER_VALUE; break; case EXPECT_HEADER_VALUE: if(':'==fc || '!'==fc || ','==fc || ';'==fc) { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } headervalue = strndup(spec+tokenstart,tokenlen); - debugf("Header value is %s\n",headervalue); + debugf("Header value is %s",headervalue); match_spec *matchlist_tail; if(speclist_tail->matches) @@ -235,23 +235,23 @@ int parse_spec(const char *spec, header_spec **output) } else { - debugf("In state %d, encountered unexpected token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In state %d, encountered unexpected token at %d len %d",state,tokenstart,tokenlen); return 0; } break; default: - debugf("In unexpected state %d with token at %d len %d\n",state,tokenstart,tokenlen); + debugf("In unexpected state %d with token at %d len %d",state,tokenstart,tokenlen); return 0; } } if(state != EXPECT_EOF_OR_SEMI_OR_HEADERKEY && state != EXPECT_EOF_OR_COMMA_OR_SEMI) { - debugf("Finished parse in unexpected state %d\n",state); + debugf("Finished parse in unexpected state %d",state); return 0; } - debugf("Parse completed successfully\n"); + debugf("Parse completed successfully"); return 1; } @@ -259,14 +259,14 @@ void free_spec(header_spec *spec) { if(!spec) return; - /* debugf("Freeing spec at %p\n",spec); */ + /* debugf("Freeing spec at %p",spec); */ free(spec->header_key); // free matches; match_spec *onematch = spec->matches; while(onematch) { - /* debugf("Freeing match with value %s\n",onematch->header_value); */ + /* debugf("Freeing match with value %s",onematch->header_value); */ free(onematch->pattern); free(onematch->header_value); match_spec *nextmatch = onematch->next; @@ -285,12 +285,12 @@ int add_matching_headers(struct curl_slist **headers, header_spec *spec, const c header_spec *onespec = spec; while(onespec) { - debugf("Testing one spec\n"); + debugf("Testing one spec"); match_spec *onematch = onespec->matches; while(onematch) { int result = fnmatch(onematch->pattern,path,0); - debugf("Testing one match, path being %s, result was %d\n",path,result); + debugf("Testing one match, path being %s, result was %d",path,result); if((result==0 && onematch->is_positive) || (result==FNM_NOMATCH && !onematch->is_positive)) { add_header(headers,onespec->header_key,onematch->header_value); @@ -298,7 +298,7 @@ int add_matching_headers(struct curl_slist **headers, header_spec *spec, const c } else if(result!=0 && result != FNM_NOMATCH) { - debugf("fnmatch error\n"); + debugf("fnmatch error"); break; } onematch = onematch->next; From b90a40844c8eac234ab2cbf6330d1e50192314f8 Mon Sep 17 00:00:00 2001 From: Erik Mackdanz Date: Sun, 8 Feb 2015 21:48:15 -0600 Subject: [PATCH 10/10] Clarify match negation --- README | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README b/README index 02b5f44..054d4bf 100644 --- a/README +++ b/README @@ -105,16 +105,18 @@ HEADER SPECIFICATIONS: The header value is set based on matching a path against a list of globs. The syntax: - HeaderKey: GlobMatch HeaderValue [, GlobMatch HeaderValue...] [; HeaderKey...] + HeaderKey: [!]GlobMatch HeaderValue [, [!]GlobMatch HeaderValue...] [; HeaderKey:...] Here's an example which sets the Content-Type header: Content-Type: /foobucket/*.html text/html, "/*/my dir/*.txt" text/plain; If no pattern matches, then the header is omitted. Note that - double quotes can be used to specify a pattern that has spaces. - Multiple headers can be specified, separated by semicolon (;) - More examples are available in the test directory. + double quotes can be used to specify a pattern that has spaces. A + match pattern preceded by ! is negated, so that the header is + applied when the path does not match the glob pattern. Multiple + headers can be specified, separated by semicolon (;) More examples + are available in the test directory. See 'man 3 fnmatch' for accepted glob patterns.