Repository: localstack/localstack

Issue:
S3 Static website Index.html returned as XML
Not sure if I'm doing something wrong  or missing something daft, but when I attempted to set up a static website on s3 and request the website, i get the html page rendered a xml in chrome. Is it because the Content-Type is application/xml? 

Expected behaviour
Using Chrome browse to http://192.168.99.100:4572/TestBucket/index.html returns my webpage rendered correctly

Steps to reproduce
from a working directory containing my index.html page.
aws --endpoint-url=http://192.168.99.100:4572 s3api create-bucket --bucket TestBucket
aws --endpoint-url=http://192.168.99.100:4752 s3 sync . s3://TestBucket --acl public-read
aws --endpoint-url=http://192.168.99.100:4752 s3 website s3://TestBucket --index-document index.html



Discussion:
same issue here. Is it because of this?
https://github.com/localstack/localstack/blob/dd47af6e4a5f753fba97eae0d32551321382b258/localstack/services/s3/s3_listener.py#L556-L557
Same issue here. I am using Developer Tools to see the Content-type in the Response. PDFs work with content-type application/pdf, images work with content-type image/jpeg. But html pages are using application/xml. I tried changing the metadata with `awslocal s3 cp s3://<your bucket name> s3://<your bucket name> --exclude "*" --include "*.html" --content-type "text/html" --metadata-directive "REPLACE" --no-guess-mime-type --recursive --acl public-read` based off of this [post](https://stackoverflow.com/questions/41015200/how-to-change-content-type-of-amazon-s3-objects) but that didn't do squat.
Hello, I have the same issue with signed urls that access html files in a S3 bucket.

The html files are served with an 'application/xml' content type.

Is there a reason for the piece of python code shown above to exist ?

EDIT: I just read the file history and saw the related issues that lead to that line of code,  but AWS S3 returns text/html content-type if we set text/html while uploading the file. localstack is not consistent with AWS IMO.
I made a short-term fix that works for my team. In s3_listener.py, change the return_response method from

    # fix content-type: https://github.com/localstack/localstack/issues/618
    #                   https://github.com/localstack/localstack/issues/549
    if 'text/html' in response.headers.get('Content-Type', ''):
        response.headers['Content-Type'] = 'application/xml; charset=utf-8'

to

    # fix content-type: https://github.com/localstack/localstack/issues/618
    #                   https://github.com/localstack/localstack/issues/549
    if 'text/html' in response.headers.get('Content-Type', '')\
            and not response_content_str.startswith('<!doctype html'):
        response.headers['Content-Type'] = 'application/xml; charset=utf-8'

This assumes your HTML file starts with `<!doctype html>`. It's not a good long-term solution, but can be used to work around the problem for the time being.
Thanks for looking into this @benvandenberg @benfonty @jtcmedia @twhitbeck @ziphrax . Took the suggestion from @benvandenberg and added the change in PR #1671 . Please report here if the problem persists.
Hi @whummer , thank you for the PR, but as I've commented in https://github.com/localstack/localstack/pull/1671/files#r337993711, the check should be case insensitive though, as per the specification.
Yep, thanks for the comment @ricoli - good point, will be fixed in a follow-up PR. 👍 

Resolving patch:
diff --git a/localstack/services/awslambda/lambda_api.py b/localstack/services/awslambda/lambda_api.py
--- a/localstack/services/awslambda/lambda_api.py
+++ b/localstack/services/awslambda/lambda_api.py
@@ -595,19 +595,23 @@ def do_list_functions():
         func_name = f_arn.split(':function:')[-1]
         arn = func_arn(func_name)
         func_details = arn_to_lambda.get(arn)
+        if not func_details:
+            # this can happen if we're accessing Lambdas from a different region (ARN mismatch)
+            continue
         funcs.append(format_func_details(func_details))
     return funcs
 
 
 def format_func_details(func_details, version=None, always_add_version=False):
     version = version or '$LATEST'
+    func_version = func_details.get_version(version)
     result = {
-        'CodeSha256': func_details.get_version(version).get('CodeSha256'),
+        'CodeSha256': func_version.get('CodeSha256'),
         'Role': func_details.role,
         'Version': version,
         'FunctionArn': func_details.arn(),
         'FunctionName': func_details.name(),
-        'CodeSize': func_details.get_version(version).get('CodeSize'),
+        'CodeSize': func_version.get('CodeSize'),
         'Handler': func_details.handler,
         'Runtime': func_details.runtime,
         'Timeout': func_details.timeout,
@@ -615,7 +619,7 @@ def format_func_details(func_details, version=None, always_add_version=False):
         'MemorySize': func_details.memory_size,
         'LastModified': func_details.last_modified,
         'TracingConfig': {'Mode': 'PassThrough'},
-        'RevisionId': func_details.get_version(version).get('RevisionId')
+        'RevisionId': func_version.get('RevisionId')
     }
     if func_details.envvars:
         result['Environment'] = {
diff --git a/localstack/services/s3/s3_listener.py b/localstack/services/s3/s3_listener.py
--- a/localstack/services/s3/s3_listener.py
+++ b/localstack/services/s3/s3_listener.py
@@ -683,7 +683,7 @@ def return_response(self, method, path, data, headers, response):
                 #                   https://github.com/localstack/localstack/issues/549
                 #                   https://github.com/localstack/localstack/issues/854
                 if 'text/html' in response.headers.get('Content-Type', '') \
-                        and not response_content_str.startswith('<!doctype html'):
+                        and not response_content_str.lower().startswith('<!doctype html'):
                     response.headers['Content-Type'] = 'application/xml; charset=utf-8'
 
                 reset_content_length = True
diff --git a/localstack/utils/aws/aws_stack.py b/localstack/utils/aws/aws_stack.py
--- a/localstack/utils/aws/aws_stack.py
+++ b/localstack/utils/aws/aws_stack.py
@@ -346,14 +346,25 @@ def dynamodb_stream_arn(table_name, latest_stream_label, account_id=None):
 
 
 def lambda_function_arn(function_name, account_id=None):
+    return lambda_function_or_layer_arn('function', function_name, account_id=account_id)
+
+
+def lambda_layer_arn(layer_name, version=None, account_id=None):
+    return lambda_function_or_layer_arn('layer', layer_name, version=None, account_id=account_id)
+
+
+def lambda_function_or_layer_arn(type, entity_name, version=None, account_id=None):
     pattern = 'arn:aws:lambda:.*:.*:(function|layer):.*'
-    if re.match(pattern, function_name):
-        return function_name
-    if ':' in function_name:
-        raise Exception('Lambda function name should not contain a colon ":"')
+    if re.match(pattern, entity_name):
+        return entity_name
+    if ':' in entity_name:
+        raise Exception('Lambda %s name should not contain a colon ":": %s' % (type, entity_name))
     account_id = get_account_id(account_id)
-    pattern = re.sub(r'\([^\|]+\|.+\)', 'function', pattern)
-    return pattern.replace('.*', '%s') % (get_region(), account_id, function_name)
+    pattern = re.sub(r'\([^\|]+\|.+\)', type, pattern)
+    result = pattern.replace('.*', '%s') % (get_region(), account_id, entity_name)
+    if version:
+        result = '%s:%s' (result, version)
+    return result
 
 
 def lambda_function_name(name_or_arn):


Test patch: