chromium/components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net.apihelpers;

import org.chromium.net.UrlResponseInfo;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

/**
 * A specialization of {@link InMemoryTransformCronetCallback} that interprets the response body as
 * a string.
 *
 * <p>The charset used to decode the string is derived from the {@code Content-Type} header.
 */
public abstract class StringCronetCallback extends InMemoryTransformCronetCallback<String> {
    private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";

    StringCronetCallback() {}

    @Override
    public StringCronetCallback addCompletionListener(
            CronetRequestCompletionListener<? super String> listener) {
        super.addCompletionListener(listener);
        return this;
    }

    @Override // Override to return the subtype
    protected String transformBodyBytes(UrlResponseInfo info, byte[] bodyBytes) {
        return new String(bodyBytes, getCharsetFromHeaders(info));
    }

    private Charset getCharsetFromHeaders(UrlResponseInfo info) {
        List<String> contentTypeHeaders = info.getAllHeaders().get(CONTENT_TYPE_HEADER_NAME);

        String charset = null;

        for (String header : contentTypeHeaders) {
            ContentTypeParametersParser parser = new ContentTypeParametersParser(header);

            while (parser.hasMore()) {
                Map.Entry<String, String> parameter;
                try {
                    parameter = parser.getNextParameter();
                } catch (ContentTypeParametersParser.ContentTypeParametersParserException e) {
                    // Malformed header, continue with the next one
                    break;
                }
                String parameterName = parameter.getKey();
                String parameterValue = parameter.getValue();

                if (charset != null && !parameterValue.equalsIgnoreCase(charset)) {
                    throw new IllegalArgumentException(
                            "Multiple charsets provided: " + parameterValue + " and " + charset);
                }
                charset = parameterValue;
            }
        }

        if (charset != null) {
            return Charset.forName(charset);
        } else {
            // Always UTF-8 on Android
            return Charset.defaultCharset();
        }
    }
}