// 
// Decompiled by Procyon v0.6.0
// 

package io.sentry.util.network;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ArrayList;
import java.net.URLDecoder;
import java.util.HashMap;
import java.io.Reader;
import io.sentry.vendor.gson.stream.JsonReader;
import java.io.StringReader;
import java.util.List;
import java.util.Locale;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import io.sentry.SentryLevel;
import org.jetbrains.annotations.NotNull;
import io.sentry.ILogger;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class NetworkBodyParser
{
    private NetworkBodyParser() {
    }
    
    @Nullable
    public static NetworkBody fromBytes(@Nullable final byte[] bytes, @Nullable final String contentType, @Nullable final String charset, final int maxSizeBytes, @NotNull final ILogger logger) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        if (contentType != null && isBinaryContentType(contentType)) {
            return new NetworkBody("[Binary data, " + bytes.length + " bytes, type: " + contentType + "]");
        }
        try {
            final String effectiveCharset = (charset != null) ? charset : "UTF-8";
            final int size = Math.min(bytes.length, maxSizeBytes);
            final boolean isPartial = bytes.length > maxSizeBytes;
            final String content = new String(bytes, 0, size, effectiveCharset);
            return parse(content, contentType, isPartial, logger);
        }
        catch (final UnsupportedEncodingException e) {
            logger.log(SentryLevel.WARNING, "Failed to decode bytes: " + e.getMessage(), new Object[0]);
            return new NetworkBody("[Failed to decode bytes, " + bytes.length + " bytes]", Collections.singletonList(NetworkBody.NetworkBodyWarning.BODY_PARSE_ERROR));
        }
    }
    
    @Nullable
    private static NetworkBody parse(@Nullable final String content, @Nullable final String contentType, final boolean isPartial, @Nullable final ILogger logger) {
        if (content == null || content.isEmpty()) {
            return null;
        }
        if (contentType != null) {
            final String lowerContentType = contentType.toLowerCase(Locale.ROOT);
            if (lowerContentType.contains("application/x-www-form-urlencoded")) {
                return parseFormUrlEncoded(content, isPartial, logger);
            }
            if (lowerContentType.contains("application/json")) {
                return parseJson(content, isPartial, logger);
            }
        }
        final List<NetworkBody.NetworkBodyWarning> warnings = isPartial ? Collections.singletonList(NetworkBody.NetworkBodyWarning.TEXT_TRUNCATED) : null;
        return new NetworkBody(content, warnings);
    }
    
    @NotNull
    private static NetworkBody parseJson(@NotNull final String content, final boolean isPartial, @Nullable final ILogger logger) {
        try (final JsonReader reader = new JsonReader(new StringReader(content))) {
            final SaferJsonParser.Result result = SaferJsonParser.parse(reader);
            final Object data = result.data;
            if (data == null && !isPartial && !result.errored && !result.hitMaxDepth) {
                final NetworkBody networkBody = new NetworkBody(null);
                reader.close();
                return networkBody;
            }
            List<NetworkBody.NetworkBodyWarning> warnings;
            if (isPartial || result.hitMaxDepth) {
                warnings = Collections.singletonList(NetworkBody.NetworkBodyWarning.JSON_TRUNCATED);
            }
            else if (result.errored) {
                warnings = Collections.singletonList(NetworkBody.NetworkBodyWarning.INVALID_JSON);
            }
            else {
                warnings = null;
            }
            return new NetworkBody(data, warnings);
        }
        catch (final Exception e) {
            if (logger != null) {
                logger.log(SentryLevel.WARNING, "Failed to parse JSON: " + e.getMessage(), new Object[0]);
            }
            return new NetworkBody(null, Collections.singletonList(NetworkBody.NetworkBodyWarning.INVALID_JSON));
        }
    }
    
    @NotNull
    private static NetworkBody parseFormUrlEncoded(@NotNull final String content, final boolean isPartial, @Nullable final ILogger logger) {
        try {
            final Map<String, Object> params = new HashMap<String, Object>();
            final String[] split;
            final String[] pairs = split = content.split("&", -1);
            for (final String pair : split) {
                final int idx = pair.indexOf("=");
                if (idx > 0) {
                    final String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8");
                    final String value = (idx < pair.length() - 1) ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : "";
                    if (params.containsKey(key)) {
                        final Object existing = params.get(key);
                        if (existing instanceof List) {
                            final List<String> list = (List<String>)existing;
                            list.add(value);
                        }
                        else {
                            final List<String> list = new ArrayList<String>();
                            list.add((String)existing);
                            list.add(value);
                            params.put(key, list);
                        }
                    }
                    else {
                        params.put(key, value);
                    }
                }
            }
            List<NetworkBody.NetworkBodyWarning> warnings;
            if (isPartial) {
                warnings = Collections.singletonList(NetworkBody.NetworkBodyWarning.TEXT_TRUNCATED);
            }
            else {
                warnings = null;
            }
            return new NetworkBody(params, warnings);
        }
        catch (final UnsupportedEncodingException e) {
            if (logger != null) {
                logger.log(SentryLevel.WARNING, "Failed to parse form data: " + e.getMessage(), new Object[0]);
            }
            return new NetworkBody(null, Collections.singletonList(NetworkBody.NetworkBodyWarning.BODY_PARSE_ERROR));
        }
    }
    
    private static boolean isBinaryContentType(@NotNull final String contentType) {
        final String lower = contentType.toLowerCase(Locale.ROOT);
        return lower.contains("image/") || lower.contains("video/") || lower.contains("audio/") || lower.contains("application/octet-stream") || lower.contains("application/pdf") || lower.contains("application/zip") || lower.contains("application/gzip");
    }
    
    private static class SaferJsonParser
    {
        private static final int MAX_DEPTH = 100;
        final Result result;
        
        private SaferJsonParser() {
            this.result = new Result();
        }
        
        @NotNull
        public static Result parse(@NotNull final JsonReader reader) {
            final SaferJsonParser parser = new SaferJsonParser();
            parser.result.data = parser.parse(reader, 0);
            return parser.result;
        }
        
        @Nullable
        private Object parse(@NotNull final JsonReader reader, final int currentDepth) {
            if (this.result.errored) {
                return null;
            }
            if (currentDepth >= 100) {
                this.result.hitMaxDepth = true;
                return null;
            }
            try {
                switch (reader.peek()) {
                    case BEGIN_OBJECT: {
                        final Map<String, Object> map = new LinkedHashMap<String, Object>();
                        try {
                            reader.beginObject();
                            while (reader.hasNext() && !this.result.errored) {
                                final String name = reader.nextName();
                                map.put(name, this.parse(reader, currentDepth + 1));
                            }
                            reader.endObject();
                        }
                        catch (final Exception e) {
                            this.result.errored = true;
                            return map;
                        }
                        return map;
                    }
                    case BEGIN_ARRAY: {
                        final List<Object> list = new ArrayList<Object>();
                        try {
                            reader.beginArray();
                            while (reader.hasNext() && !this.result.errored) {
                                list.add(this.parse(reader, currentDepth + 1));
                            }
                            reader.endArray();
                        }
                        catch (final Exception e2) {
                            this.result.errored = true;
                            return list;
                        }
                        return list;
                    }
                    case STRING: {
                        return reader.nextString();
                    }
                    case NUMBER: {
                        return reader.nextDouble();
                    }
                    case BOOLEAN: {
                        return reader.nextBoolean();
                    }
                    case NULL: {
                        reader.nextNull();
                        return null;
                    }
                    default: {
                        this.result.errored = true;
                        return null;
                    }
                }
            }
            catch (final Exception ignored) {
                this.result.errored = true;
                return null;
            }
        }
        
        private static class Result
        {
            @Nullable
            private Object data;
            private boolean hitMaxDepth;
            private boolean errored;
        }
    }
}
