<commit_before>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cekrlic.jlesscss;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * Filter to process .css requests which are actually LESS files, doing on the
 * server side what less.js normally does on the client side.
 */
@WebFilter(
		filterName = "LessCSSFilter",
		urlPatterns = "*.less"
)
public class LessCSSFilter implements Filter {
	private static final Logger log = LoggerFactory.getLogger(LessCSSFilter.class);
	private static final String[] DEFAULT_PATTERNS = new String[]{
			"EEE, dd MMM yyyy HH:mm:ss Z", // PATTERN_RFC1123
			"EEE, dd MMM yyyy HH:mm:ss zzz", // PATTERN_RFC1123
			"EEE, dd-MMM-yy HH:mm:ss zzz", // PATTERN_RFC1036
			"EEE MMM d HH:mm:ss yyyy", // PATTERN_ASCTIME
	};
	static final String COOKIE_NAME = "i";

	private Compiler compiler;

	public void init(FilterConfig filterConfig) {
		compiler = Compiler.getCompiler();
	}

	@Override
	public void destroy() {

	}

	/**
	 * Parse the LESS file, do some caching to make sure we don't compile needlessly.
	 * @param req The request
	 * @param res The response
	 * @param chain The filterChain
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

		res.setCharacterEncoding("UTF-8");
		res.setContentType("text/css; charset=UTF-8");

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		long ifModifiedSince = request.getDateHeader("If-Modified-Since");
		if (ifModifiedSince > 0) {
			request = new LastModifiedHttpServletRequestWrapper(request);
		}

		// Capture the resource file
		ResourceResponseWrapper wrapper = new ResourceResponseWrapper(response);
		chain.doFilter(request, wrapper);

		String ct = response.getContentType().toLowerCase().trim();

		if(!ct.startsWith("text/css") && !ct.startsWith("text/less")) {
			if(ct.startsWith("application/octet-stream")) {
				log.debug("File types not defined for *.less, returning application/octet-stream, but expected text/less. Please configure your app server.");
				res.setContentType("text/css; charset=UTF-8");
			} else {
				res.getOutputStream().write(wrapper.toByteArray());
			}
		}

		// Stop processing if not all good
		if (wrapper.getStatus() != HttpServletResponse.SC_OK) {
			return;
		}

		res.setCharacterEncoding("UTF-8");
		log.info("Processing request for: {}", request.getServletPath());

		long lastModified;
		try {
			lastModified = LessCSSFilter.parseDate(response.getHeader("Last-Modified")).getTime();
		} catch (ParseException e) {
			throw new ServletException(e);
		}

		if (ifModifiedSince > 0) {
			Cookie includes = null;
			for(Cookie c: request.getCookies()) {
				if(COOKIE_NAME.equalsIgnoreCase(c.getName())) {
					includes = c;
					break;
				}
			}

			if(includes != null) {
				// Check included files
				for(String path: decrypt(includes.getValue()).split(",")) {
					log.info("Checking last modified for: {}", path);
					LastModifiedResponseWrapper lmrw = new LastModifiedResponseWrapper(response);
					RequestDispatcher rd = request.getRequestDispatcher(path);
					ResourceResponseWrapper resp = new ResourceResponseWrapper(response);
					rd.include(request, resp);
					try {
						lastModified = parseDate(lmrw.getHeader("Last-Modified")).getTime();
					} catch (ParseException e) {
						throw new ServletException(e);
					}

				}
			}


			if(lastModified <= ifModifiedSince) {
				log.debug("Last-Modified request for {} > returning NOT_MODIFIED", request.getServletPath());
				response.setContentLength(0);
				response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
				return;
			} else {
				log.debug("Something modified after {}, proceeding with full response.", ifModifiedSince);
			}
		}

		log.debug("Setting Last-Modified to: {} for {}.", new java.util.Date(lastModified), request.getServletPath());
		response.setDateHeader("Last-Modified", lastModified);

		List<String> files = new ArrayList<>(16);
		HtmlImporter importer = new HtmlImporter(request, response);
		importer.setFileImportedCallback(new Importer.ListFileImporterCallback(files));
		Source source = new MemorySource(request.getServletPath(), wrapper.toString());

		String result = compiler.compile(source, importer);

		// Write processed result to response
		final byte[] outbytes = result.getBytes(wrapper.getCharacterEncoding());

		Cookie c = new Cookie(COOKIE_NAME, encrypt(join(files, ",")));
		c.setHttpOnly(true);
		c.setPath(request.getRequestURI());
		c.setMaxAge(24 * 3600 * 365);
		response.addCookie(c);
		response.setContentLength(outbytes.length);
		response.getOutputStream().write(outbytes);
	}

	private String encrypt(String join) {
		try {
			return javax.xml.bind.DatatypeConverter.printBase64Binary(join.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			return javax.xml.bind.DatatypeConverter.printBase64Binary(join.getBytes());
		}
	}

	private String decrypt(String value) {
		try {
			return new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(value), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(value));
		}
	}

	private String join(List<String> files, String separator) {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for(String s: files) {
			if(first) {
				first = false;
			} else {
				sb.append(separator);
			}
			sb.append(s);
		}
		return sb.toString();
	}
<commit_msg>Fix for cookies<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cekrlic.jlesscss;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * Filter to process .css requests which are actually LESS files, doing on the
 * server side what less.js normally does on the client side.
 */
@WebFilter(
		filterName = "LessCSSFilter",
		urlPatterns = "*.less"
)
public class LessCSSFilter implements Filter {
	private static final Logger log = LoggerFactory.getLogger(LessCSSFilter.class);
	private static final String[] DEFAULT_PATTERNS = new String[]{
			"EEE, dd MMM yyyy HH:mm:ss Z", // PATTERN_RFC1123
			"EEE, dd MMM yyyy HH:mm:ss zzz", // PATTERN_RFC1123
			"EEE, dd-MMM-yy HH:mm:ss zzz", // PATTERN_RFC1036
			"EEE MMM d HH:mm:ss yyyy", // PATTERN_ASCTIME
	};
	static final String COOKIE_NAME = "i";

	private Compiler compiler;

	public void init(FilterConfig filterConfig) {
		compiler = Compiler.getCompiler();
	}

	@Override
	public void destroy() {

	}

	/**
	 * Parse the LESS file, do some caching to make sure we don't compile needlessly.
	 * @param req The request
	 * @param res The response
	 * @param chain The filterChain
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

		res.setCharacterEncoding("UTF-8");
		res.setContentType("text/css; charset=UTF-8");

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		long ifModifiedSince = request.getDateHeader("If-Modified-Since");
		if (ifModifiedSince > 0) {
			request = new LastModifiedHttpServletRequestWrapper(request);
		}

		// Capture the resource file
		ResourceResponseWrapper wrapper = new ResourceResponseWrapper(response);
		chain.doFilter(request, wrapper);

		String ct = response.getContentType().toLowerCase().trim();

		if(!ct.startsWith("text/css") && !ct.startsWith("text/less")) {
			if(ct.startsWith("application/octet-stream")) {
				log.debug("File types not defined for *.less, returning application/octet-stream, but expected text/less. Please configure your app server.");
				res.setContentType("text/css; charset=UTF-8");
			} else {
				res.getOutputStream().write(wrapper.toByteArray());
			}
		}

		// Stop processing if not all good
		if (wrapper.getStatus() != HttpServletResponse.SC_OK) {
			return;
		}

		res.setCharacterEncoding("UTF-8");
		log.info("Processing request for: {}", request.getServletPath());

		long lastModified;
		try {
			lastModified = LessCSSFilter.parseDate(response.getHeader("Last-Modified")).getTime();
		} catch (ParseException e) {
			throw new ServletException(e);
		}

		if (ifModifiedSince > 0) {
			Cookie includes = null;
			for(Cookie c: request.getCookies()) {
				if(COOKIE_NAME.equalsIgnoreCase(c.getName())) {
					includes = c;
					break;
				}
			}

			if(includes != null) {
				// Check included files
				for(String path: decrypt(includes.getValue()).split(",")) {
					log.info("Checking last modified for: {}", path);
					LastModifiedResponseWrapper lmrw = new LastModifiedResponseWrapper(response);
					RequestDispatcher rd = request.getRequestDispatcher(path);
					ResourceResponseWrapper resp = new ResourceResponseWrapper(response);
					rd.include(request, resp);
					try {
						lastModified = parseDate(lmrw.getHeader("Last-Modified")).getTime();
					} catch (ParseException e) {
						throw new ServletException(e);
					}

				}
			}


			if(lastModified <= ifModifiedSince) {
				log.debug("Last-Modified request for {} > returning NOT_MODIFIED", request.getServletPath());
				response.setContentLength(0);
				response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
				return;
			} else {
				log.debug("Something modified after {}, proceeding with full response.", ifModifiedSince);
			}
		}

		log.debug("Setting Last-Modified to: {} for {}.", new java.util.Date(lastModified), request.getServletPath());
		response.setDateHeader("Last-Modified", lastModified);

		List<String> files = new ArrayList<>(16);
		HtmlImporter importer = new HtmlImporter(request, response);
		importer.setFileImportedCallback(new Importer.ListFileImporterCallback(files));
		Source source = new MemorySource(request.getServletPath(), wrapper.toString());

		String result = compiler.compile(source, importer);

		// Write processed result to response
		final byte[] outbytes = result.getBytes(wrapper.getCharacterEncoding());

		Cookie c = new Cookie(COOKIE_NAME, encrypt(join(files, ",")));
		c.setHttpOnly(true);
		c.setPath(request.getRequestURI());
		c.setMaxAge(24 * 3600 * 365);
		response.addCookie(c);
		response.setContentLength(outbytes.length);
		response.getOutputStream().write(outbytes);
	}

	private String encrypt(String join) {
		String result;
		try {
			result = javax.xml.bind.DatatypeConverter.printBase64Binary(join.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			result = javax.xml.bind.DatatypeConverter.printBase64Binary(join.getBytes());
		}
		// Most browsers will choke if cookie includes an equals sign
		return result.replace('=', '#');
	}

	private String decrypt(String value) {
		// Return the equals since to be able to decypt the content
		value = value.replace('#', '=');
		try {
			return new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(value), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(value));
		}
	}

	private String join(List<String> files, String separator) {
		StringBuilder sb = new StringBuilder();
		boolean first = true;
		for(String s: files) {
			if(first) {
				first = false;
			} else {
				sb.append(separator);
			}
			sb.append(s);
		}
		return sb.toString();
	}

{'fraction_non_alphanumeric': 0.1182339364157546, 'fraction_numerical': 0.006849825031643214, 'mean_word_length': 4.383567134268537, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, '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': '3873400', 'n_tokens_mistral': 4467, 'n_tokens_neox': 3927, 'n_words': 1021}