diff --git a/request_decision_graph.yml b/request_decision_graph.yml index 06dffd7..fa13eb5 100644 --- a/request_decision_graph.yml +++ b/request_decision_graph.yml @@ -3,6 +3,9 @@ start: garbage: - '{random 10-3000 printable}' - '{random 10-1000 binary}' + garbage_no_whitespace: + - '{random 10-3000 printable_no_whitespace}' + - '{random 10-1000 binary_no_whitespace}' choices: - bucket @@ -25,7 +28,7 @@ bucket_garbage_method: - '{bucket_not_readable}' - '{bucket_writable}' - '{bucket_not_writable}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: - bucket_get_simple - bucket_get_filtered @@ -40,12 +43,12 @@ bucket_delete: bucket: - '{bucket_writable}' - '{bucket_not_writable}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' query: - null - policy - website - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: [] bucket_get: @@ -54,7 +57,7 @@ bucket_get: bucket: - '{bucket_readable}' - '{bucket_not_readable}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: - 11 bucket_get_simple - bucket_get_filtered @@ -72,56 +75,56 @@ bucket_get_simple: - requestPayment - versioning - website - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: [] bucket_get_uploads: set: delimiter: - null - - '3 delimiter={garbage}' + - '3 delimiter={garbage_no_whitespace}' prefix: - null - - '3 prefix={garbage}' + - '3 prefix={garbage_no_whitespace}' key_marker: - null - 'key-marker={object_readable}' - 'key-marker={object_not_readable}' - 'key-marker={invalid_key}' - - 'key-marker={random 100-1000 printable}' + - 'key-marker={random 100-1000 printable_no_whitespace}' max_uploads: - null - - 'max-uploads={random 1-5 binary}' + - 'max-uploads={random 1-5 binary_no_whitespace}' - 'max-uploads={random 1-1000 digits}' upload_id_marker: - null - - '3 upload-id-marker={random}' + - '3 upload-id-marker={random 0-1000 printable_no_whitespace}' query: - 'uploads' - 'uploads&{delimiter}&{prefix}' - 'uploads&{max_uploads}&{key_marker}&{upload_id_marker}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: [] bucket_get_filtered: set: delimiter: - - 'delimiter={garbage}' + - 'delimiter={garbage_no_whitespace}' prefix: - - 'prefix={garbage}' + - 'prefix={garbage_no_whitespace}' marker: - 'marker={object_readable}' - 'marker={object_not_readable}' - 'marker={invalid_key}' - - 'marker={random 100-1000 printable}' + - 'marker={random 100-1000 printable_no_whitespace}' max_keys: - - 'max-keys={random 1-5 binary}' + - 'max-keys={random 1-5 binary_no_whitespace}' - 'max-keys={random 1-1000 digits}' query: - null - '{delimiter}&{prefix}' - '{max-keys}&{marker}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' choices: [] bucket_put: @@ -129,7 +132,7 @@ bucket_put: bucket: - '{bucket_writable}' - '{bucket_not_writable}' - - '2 {garbage}' + - '2 {garbage_no_whitespace}' method: PUT choices: - bucket_put_simple @@ -142,9 +145,14 @@ bucket_put_create: - '2 {garbage}' - '{random 2-10 binary}' acl: - - private + - 'private' + - 'public-read' + - 'public-read-write' + - 'authenticated-read' + - 'bucket-owner-read' + - 'bucket-owner-full-control' - '{random 3000 letters}' - - '{random 100-1000 binary}' + - '{random 100-1000 binary_no_whitespace}' headers: - ['0-1', 'x-amz-acl', '{acl}'] choices: [] @@ -163,7 +171,7 @@ bucket_put_versioning: - '{random 2-10 binary}' - '{random 2000-3000 printable}' mfa_header: - - '{random 10-1000 printable} {random 10-1000 printable}' + - '{random 10-1000 printable_no_whitespace} {random 10-1000 printable_no_whitespace}' headers: - ['0-1', 'x-amz-mfa', '{mfa_header}'] choices: [] @@ -225,7 +233,7 @@ bucket_put_simple: notification_body: - null - '' - - '2 {topic}{event}' + - '2 {topic}{event}' topic: - null - '2 {garbage}' diff --git a/s3tests/functional/test_fuzzer.py b/s3tests/functional/test_fuzzer.py index eb2ab1f..1039b55 100644 --- a/s3tests/functional/test_fuzzer.py +++ b/s3tests/functional/test_fuzzer.py @@ -158,6 +158,20 @@ def test_expand_random_binary(): eq(got, '\xdfj\xf1\xd80>a\xcd\xc4\xbb') +def test_expand_random_printable_no_whitespace(): + prng = random.Random(1) + for _ in xrange(1000): + got = expand({}, '{random 500 printable_no_whitespace}', prng) + assert_true(reduce(lambda x, y: x and y, [x not in string.whitespace and x in string.printable for x in got])) + + +def test_expand_random_binary(): + prng = random.Random(1) + for _ in xrange(1000): + got = expand({}, '{random 500 binary_no_whitespace}', prng) + assert_true(reduce(lambda x, y: x and y, [x not in string.whitespace for x in got])) + + def test_expand_random_no_args(): prng = random.Random(1) for _ in xrange(1000): diff --git a/s3tests/fuzz_headers.py b/s3tests/fuzz_headers.py index 79a8d47..6e581b4 100644 --- a/s3tests/fuzz_headers.py +++ b/s3tests/fuzz_headers.py @@ -136,6 +136,7 @@ def expand(decision, value, prng): class RepeatExpandingFormatter(string.Formatter): charsets = { + 'printable_no_whitespace': string.printable.translate(None, string.whitespace), 'printable': string.printable, 'punctuation': string.punctuation, 'whitespace': string.whitespace, @@ -161,8 +162,7 @@ class RepeatExpandingFormatter(string.Formatter): if self._recursion > 5: raise RecursionError(key) fmt = self.__class__(self.prng, _recursion=self._recursion+1) - # must use vformat not **kwargs so our SpecialVariables is not - # downgraded to just a dict + n = fmt.vformat(val, args, kwargs) return n @@ -185,10 +185,12 @@ class RepeatExpandingFormatter(string.Formatter): except IndexError: charset_arg = 'printable' - if charset_arg == 'binary': + if charset_arg == 'binary' or charset_arg == 'binary_no_whitespace': num_bytes = length + 8 tmplist = [self.prng.getrandbits(64) for _ in xrange(num_bytes / 8)] tmpstring = struct.pack((num_bytes / 8) * 'Q', *tmplist) + if charset_arg == 'binary_no_whitespace': + tmpstring = ''.join(c for c in tmpstring if c not in string.whitespace) return tmpstring[0:length] else: charset = self.charsets[charset_arg] @@ -198,14 +200,16 @@ class RepeatExpandingFormatter(string.Formatter): def parse_options(): parser = OptionParser() parser.add_option('-O', '--outfile', help='write output to FILE. Defaults to STDOUT', metavar='FILE') - parser.add_option('--seed', dest='seed', type='int', help='initial seed for the random number generator', metavar='SEED') + parser.add_option('--seed', dest='seed', type='int', help='initial seed for the random number generator') parser.add_option('--seed-file', dest='seedfile', help='read seeds for specific requests from FILE', metavar='FILE') parser.add_option('-n', dest='num_requests', type='int', help='issue NUM requests before stopping', metavar='NUM') parser.add_option('-v', '--verbose', dest='verbose', action="store_true", help='turn on verbose output') parser.add_option('-d', '--debug', dest='debug', action="store_true", help='turn on debugging (very verbose) output') - parser.add_option('--decision-graph', dest='graph_filename', help='file in which to find the request decision graph', metavar='NUM') + parser.add_option('--decision-graph', dest='graph_filename', help='file in which to find the request decision graph') + parser.add_option('--no-cleanup', dest='cleanup', action="store_false", help='turn off teardown so you can peruse the state of buckets after testing') parser.set_defaults(num_requests=5) + parser.set_defaults(cleanup=True) parser.set_defaults(graph_filename='request_decision_graph.yml') return parser.parse_args() @@ -317,10 +321,10 @@ def _main(): except KeyError: headers = {} - print>>VERBOSE, "%s %s" %(method[:100], path[:100]) + print>>VERBOSE, "%r %r" %(method[:100], path[:100]) for h, v in headers.iteritems(): - print>>VERBOSE, "%s: %s" %(h[:50], v[:50]) - print>>VERBOSE, "%s\n" % body[:100] + print>>VERBOSE, "%r: %r" %(h[:50], v[:50]) + print>>VERBOSE, "%r\n" % body[:100] print>>DEBUG, 'FULL REQUEST' print>>DEBUG, 'Method: %r' %method @@ -333,13 +337,22 @@ def _main(): #response = s3_connection.make_request(method, path, data=body, headers=headers, override_num_retries=0) response = s3_connection.make_request(method, path, data=body, headers=headers) + failed = True if response.status in [500, 503] else False + if failed: + print>>OUT, 'FAILED:' + OLD_VERBOSE = VERBOSE + OLD_DEBUG = DEBUG + VERBOSE = DEBUG = OUT print>>VERBOSE, 'Response status code: %d %s' %(response.status, response.reason) print>>DEBUG, 'Body:\n%s' %response.read() - if response.status == 500 or response.status == 503: - print>>OUT, 'FAILED:\n%s' %request print>>VERBOSE, '='*80 + if failed: + VERBOSE = OLD_VERBOSE + DEBUG = OLD_DEBUG print>>OUT, '...done fuzzing' - common.teardown() + + if options.cleanup: + common.teardown() def main():