<commit_before>package io.myweb;

import android.content.Context;
import android.net.LocalSocket;
import android.util.Log;
import io.myweb.api.Cookie;
import io.myweb.api.Cookies;
import io.myweb.api.Headers;

import java.io.*;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class Endpoint {

	public static final int OUTPUT_STREAM_BUFFER_SIZE = 32 * 1024;

	private Context context;

	public Endpoint(Context context) {
		this.context = context;
	}

	public abstract void invoke(String uri, String request, LocalSocket localSocket, String reqId) throws Exception;

	protected Context getContext() {
		return context;
	}

	protected abstract Pattern getPattern();

	protected Matcher matcher(String uri) {
		return getPattern().matcher(uri);
	}

	protected void writeResponseHeaders(OutputStream os, String reqId) throws IOException {
		os.write((reqId + "\n").getBytes());
		os.write("HTTP/1.1 200 OK\r\n".getBytes());
		os.write("Connection: keep-alive\r\n".getBytes());
	}

	protected OutputStream outputStream(LocalSocket localSocket) throws IOException {
		return new BufferedOutputStream(localSocket.getOutputStream(), OUTPUT_STREAM_BUFFER_SIZE);
	}

	public boolean match(String method, String uri) {
		String urlNoQueryParams = urlNoQueryParams(uri);
		Matcher m = matcher(urlNoQueryParams);
		boolean matched = httpMethod().equals(method) && m.matches();
		if (matched) {
			Log.d("Endpoint", "matched path " + httpMethod() + " " + originalPath() + " (pattern: " + getPattern() + ") request: " + method + " " + uri);
		} else {
			Log.d("Endpoint", "not matched path " + httpMethod() + " " + originalPath() + " (pattern: " + getPattern() + ") request: " + method + " " + uri);
		}
		return matched;
	}

	protected abstract String originalPath();

	protected abstract String httpMethod();

	protected ActualParam[] actualParams(String uri, String request, FormalParam[] formalParams, Map<String, String> defaultQueryParams, Map<String, Integer> groupMap, Context ctx) throws Exception {
		String urlNoQueryParams = urlNoQueryParams(uri);
		Map<String, String> paramsMap;
		if ("POST".equals(httpMethod())) {
			paramsMap = decodeQueryString(body(request));
		} else {
			paramsMap = decodeQueryString(queryParams(uri));
		}
		Matcher m = matcher(urlNoQueryParams);
		ActualParam[] actualParams = new ActualParam[formalParams.length];
		if (m.matches()) {
			for (FormalParam fp : formalParams) {
				int fpId = fp.getId();
				Class<?> fpClazz = classForName(fp.getTypeName());
				String fpName = fp.getName();
				if (groupMap.containsKey(fpName)) {
					int urlGroupIdx = groupMap.get(fpName);
					String val = m.group(urlGroupIdx);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				} else if (Context.class.equals(fpClazz)) {
					actualParams[fpId] = new ActualParam(Context.class, ctx);
				} else if (Cookies.class.equals(fpClazz)){
					actualParams[fpId] = new ActualParam(Cookies.class, parseCookies(request));
				} else if (Cookie.class.equals(fpClazz)) {
					Cookie cookie = parseCookies(request).getCookie(fpName);
					actualParams[fpId] = new ActualParam(Cookie.class, cookie);
				} else if (Headers.class.equals(fpClazz)) {
					actualParams[fpId] = new ActualParam(Headers.class, headersObject(request));
				} else if (paramsMap.containsKey(fpName)) {
					String val = paramsMap.get(fpName);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				} else if (defaultQueryParams.containsKey(fpName)) {
					String val = defaultQueryParams.get(fpName);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				}
			}
		} else {
			throw new Exception("couldn't match URI: '" + uri + "' with pattern '" + getPattern() + "' (BTW, this shouldn't happen...)");
		}
		return actualParams;
	}

	private Cookies parseCookies(String request) throws UnsupportedEncodingException {
		String[] headers = headers(request).split("\\r\\n");
		Map<String, Cookie> cookies = new HashMap<String, Cookie>();
		for (String header : headers) {
			String[] hv = header.split(":");
			if (hv.length == 2) {
				if ("Cookie".equals(hv[0])) {
					Cookie cookie = parseCookie(hv[1]);
					cookies.put(cookie.getName(), cookie);
				}
			}
		}
		return new Cookies(cookies);
	}

	private Cookie parseCookie(String cookieStr) throws UnsupportedEncodingException {
		String[] nv = cookieStr.split("=");
		if (nv.length == 2) {
			String value = nv[1].trim();
			String decodedVal = URLDecoder.decode(value, "UTF-8");
			return new Cookie(nv[0].trim(), decodedVal);
		} else {
			throw new RuntimeException("invalid cookie: " + cookieStr);
		}
	}

	private String body(String request) {
		return request.substring(bodyStartIndex(request));
	}

	private int bodyStartIndex(String request) {
		return request.indexOf("\r\n\r\n") + 4;
	}

	private String headers(String request) {
		return request.substring(0, bodyStartIndex(request));
	}

	private Headers headersObject(String request) {
		String headersString = headers(request);
		String[] headersArr = headersString.split("\\r\\n");
		Map<String, List<String>> headersMap = new HashMap<String, List<String>>();
		for (int i = 1; i < headersArr.length; i++) {
			int valIdx = headersArr[i].indexOf(": ");
			String key = headersArr[i].substring(0, valIdx);
			String val = headersArr[i].substring(valIdx + 2);
			insert(headersMap, key, val);
		}
		return new Headers(headersMap);
	}

	private void insert(Map<String, List<String>> headersMap, String key, String val) {
		List<String> values;
		if (headersMap.containsKey(key)) {
			values = headersMap.get(key);
		} else {
			values = new LinkedList<String>();
			headersMap.put(key, values);
		}
		values.add(val);<commit_msg>Simplify cookie parsing<commit_after>package io.myweb;

import android.content.Context;
import android.net.LocalSocket;
import android.util.Log;
import io.myweb.api.Cookie;
import io.myweb.api.Cookies;
import io.myweb.api.Headers;

import java.io.*;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class Endpoint {

	public static final int OUTPUT_STREAM_BUFFER_SIZE = 32 * 1024;

	private Context context;

	public Endpoint(Context context) {
		this.context = context;
	}

	public abstract void invoke(String uri, String request, LocalSocket localSocket, String reqId) throws Exception;

	protected Context getContext() {
		return context;
	}

	protected abstract Pattern getPattern();

	protected Matcher matcher(String uri) {
		return getPattern().matcher(uri);
	}

	protected void writeResponseHeaders(OutputStream os, String reqId) throws IOException {
		os.write((reqId + "\n").getBytes());
		os.write("HTTP/1.1 200 OK\r\n".getBytes());
		os.write("Connection: keep-alive\r\n".getBytes());
	}

	protected OutputStream outputStream(LocalSocket localSocket) throws IOException {
		return new BufferedOutputStream(localSocket.getOutputStream(), OUTPUT_STREAM_BUFFER_SIZE);
	}

	public boolean match(String method, String uri) {
		String urlNoQueryParams = urlNoQueryParams(uri);
		Matcher m = matcher(urlNoQueryParams);
		boolean matched = httpMethod().equals(method) && m.matches();
		if (matched) {
			Log.d("Endpoint", "matched path " + httpMethod() + " " + originalPath() + " (pattern: " + getPattern() + ") request: " + method + " " + uri);
		} else {
			Log.d("Endpoint", "not matched path " + httpMethod() + " " + originalPath() + " (pattern: " + getPattern() + ") request: " + method + " " + uri);
		}
		return matched;
	}

	protected abstract String originalPath();

	protected abstract String httpMethod();

	protected ActualParam[] actualParams(String uri, String request, FormalParam[] formalParams, Map<String, String> defaultQueryParams, Map<String, Integer> groupMap, Context ctx) throws Exception {
		String urlNoQueryParams = urlNoQueryParams(uri);
		Map<String, String> paramsMap;
		if ("POST".equals(httpMethod())) {
			paramsMap = decodeQueryString(body(request));
		} else {
			paramsMap = decodeQueryString(queryParams(uri));
		}
		Matcher m = matcher(urlNoQueryParams);
		ActualParam[] actualParams = new ActualParam[formalParams.length];
		if (m.matches()) {
			for (FormalParam fp : formalParams) {
				int fpId = fp.getId();
				Class<?> fpClazz = classForName(fp.getTypeName());
				String fpName = fp.getName();
				if (groupMap.containsKey(fpName)) {
					int urlGroupIdx = groupMap.get(fpName);
					String val = m.group(urlGroupIdx);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				} else if (Context.class.equals(fpClazz)) {
					actualParams[fpId] = new ActualParam(Context.class, ctx);
				} else if (Cookies.class.equals(fpClazz)){
					actualParams[fpId] = new ActualParam(Cookies.class, parseCookies(request));
				} else if (Cookie.class.equals(fpClazz)) {
					Cookie cookie = parseCookies(request).getCookie(fpName);
					actualParams[fpId] = new ActualParam(Cookie.class, cookie);
				} else if (Headers.class.equals(fpClazz)) {
					actualParams[fpId] = new ActualParam(Headers.class, headers(request));
				} else if (paramsMap.containsKey(fpName)) {
					String val = paramsMap.get(fpName);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				} else if (defaultQueryParams.containsKey(fpName)) {
					String val = defaultQueryParams.get(fpName);
					Object convertedVal = convert(val, fp.getTypeName());
					actualParams[fpId] = new ActualParam(fpClazz, convertedVal);
				}
			}
		} else {
			throw new Exception("couldn't match URI: '" + uri + "' with pattern '" + getPattern() + "' (BTW, this shouldn't happen...)");
		}
		return actualParams;
	}

	private Cookies parseCookies(String request) throws UnsupportedEncodingException {
		Headers headers = headers(request);
		List<String> cookies = headers.getAll("Cookie");
		Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
		for (String cookieStr : cookies) {
			Cookie cookie = parseCookie(cookieStr);
			cookieMap.put(cookie.getName(), cookie);
		}
		return new Cookies(cookieMap);
	}

	private Cookie parseCookie(String cookieStr) throws UnsupportedEncodingException {
		String[] nv = cookieStr.split("=");
		if (nv.length == 2) {
			String value = nv[1].trim();
			String decodedVal = URLDecoder.decode(value, "UTF-8");
			return new Cookie(nv[0].trim(), decodedVal);
		} else {
			throw new RuntimeException("invalid cookie: " + cookieStr);
		}
	}

	private String body(String request) {
		return request.substring(bodyStartIndex(request));
	}

	private int bodyStartIndex(String request) {
		return request.indexOf("\r\n\r\n") + 4;
	}

	private String headersStr(String request) {
		return request.substring(0, bodyStartIndex(request));
	}

	private Headers headers(String request) {
		String headersStr = headersStr(request);
		String[] headersArr = headersStr.split("\\r\\n");
		Map<String, List<String>> headersMap = new HashMap<String, List<String>>();
		for (int i = 1; i < headersArr.length; i++) {
			int valIdx = headersArr[i].indexOf(": ");
			String key = headersArr[i].substring(0, valIdx);
			String val = headersArr[i].substring(valIdx + 2);
			insert(headersMap, key, val);
		}
		return new Headers(headersMap);
	}

	private void insert(Map<String, List<String>> headersMap, String key, String val) {
		List<String> values;
		if (headersMap.containsKey(key)) {
			values = headersMap.get(key);
		} else {
			values = new LinkedList<String>();
			headersMap.put(key, values);
		}
		values.add(val);
{'fraction_non_alphanumeric': 0.13654215760006685, 'fraction_numerical': 0.00359321467368597, 'mean_word_length': 4.584694353709753, 'pattern_counts': {'":': 3, '<': 34, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3745036', 'n_tokens_mistral': 4108, 'n_tokens_neox': 3637, 'n_words': 880}