diff --git a/.gitmodules b/.gitmodules index e3bc8c515..bb6773795 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,9 @@ [submodule "jodd-json"] path = jodd-json url = https://github.com/oblac/jodd-json +[submodule "jodd-http"] + path = jodd-http + url = https://github.com/oblac/jodd-http +[submodule "jodd-props"] + path = jodd-props + url = https://github.com/oblac/jodd-props diff --git a/build.gradle b/build.gradle index b174254bf..60c13ffc2 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,7 @@ description = ''' ''' //version = '5.1.6-' + date() -version = '5.2.0' +version = '5.3.0' // --- properties ------------------------------------------------------------- diff --git a/jodd-core/src/main/java/jodd/io/StreamGobbler.java b/jodd-core/src/main/java/jodd/io/StreamGobbler.java deleted file mode 100644 index e9bc44043..000000000 --- a/jodd-core/src/main/java/jodd/io/StreamGobbler.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io; - -import jodd.util.StringPool; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintStream; - -/** - * Consumes a stream. - * For any Process, the input and error streams must read even - * if the data written to these streams is not used by the application. - * The generally accepted solution for this problem is a stream gobbler thread - * that does nothing but consume data from an input stream until stopped. - */ -public class StreamGobbler extends Thread { - - protected final InputStream is; - protected final String prefix; - protected final OutputStream out; - protected final Object lock = new Object(); - protected boolean end = false; - - public StreamGobbler(final InputStream is) { - this(is, null, null); - } - - public StreamGobbler(final InputStream is, final OutputStream output) { - this(is, output, null); - } - - public StreamGobbler(final InputStream is, final OutputStream output, final String prefix) { - this.is = is; - this.prefix = prefix; - this.out = output; - } - - @Override - public void run() { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - - try { - String line; - while ((line = br.readLine()) != null) { - if (out != null) { - if (prefix != null) { - out.write(prefix.getBytes()); - } - out.write(line.getBytes()); - out.write(StringPool.BYTES_NEW_LINE); - } - } - } - catch (IOException ioe) { - if (out != null) { - ioe.printStackTrace(new PrintStream(out)); - } - } - finally { - if (out != null) { - try { - out.flush(); - } - catch (IOException ignore) { - } - } - try { - br.close(); - } - catch (IOException ignore) { - } - } - - synchronized (lock) { - lock.notifyAll(); - end = true; - } - } - - /** - * Waits for gobbler to end. - */ - public void waitFor() { - try { - synchronized (lock) { - if (!end) { - lock.wait(); - } - } - } - catch (InterruptedException ignore) { - Thread.currentThread().interrupt(); - } - } - -} \ No newline at end of file diff --git a/jodd-core/src/main/java/jodd/io/upload/FileUpload.java b/jodd-core/src/main/java/jodd/io/upload/FileUpload.java deleted file mode 100644 index 395adcb02..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/FileUpload.java +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Encapsulates base for uploaded file. Its instance may be - * either valid, when it represent an uploaded file, or invalid - * when uploaded file doesn't exist or there was a problem with it. - */ -public abstract class FileUpload { - - protected final MultipartRequestInputStream input; - protected final int maxFileSize; - protected final FileUploadHeader header; - - protected FileUpload(final MultipartRequestInputStream input, final int maxFileSize) { - this.input = input; - this.header = input.lastHeader; - this.maxFileSize = maxFileSize; - } - - // ---------------------------------------------------------------- header - - /** - * Returns {@link FileUploadHeader} of uploaded file. - */ - public FileUploadHeader getHeader() { - return header; - } - - // ---------------------------------------------------------------- data - - /** - * Returns all bytes of uploaded file. - */ - public abstract byte[] getFileContent() throws IOException; - - /** - * Returns input stream of uploaded file. - */ - public abstract InputStream getFileInputStream() throws IOException; - - // ---------------------------------------------------------------- size and validity - - protected boolean valid; - - protected int size = -1; - - protected boolean fileTooBig; - - /** - * Returns the file upload size or -1 if file was not uploaded. - */ - public int getSize() { - return size; - } - - /** - * Returns true if file was uploaded. - */ - public boolean isUploaded() { - return size != -1; - } - - /** - * Returns true if upload process went correctly. - * This still does not mean that file is uploaded, e.g. when file - * was not specified on the form. - */ - public boolean isValid() { - return valid; - } - - /** - * Returns max file size or -1 if there is no max file size. - */ - public int getMaxFileSize() { - return maxFileSize; - } - - /** - * Returns true if file is too big. File will be marked as invalid. - */ - public boolean isFileTooBig() { - return fileTooBig; - } - - // ---------------------------------------------------------------- status - - /** - * Returns true if uploaded file content is stored in memory. - */ - public abstract boolean isInMemory(); - - // ---------------------------------------------------------------- process - - /** - * Process request input stream. Note that file size is unknown at this point. - * Therefore, the implementation should set the {@link #getSize() size} - * attribute after successful processing. This method also must set the - * {@link #isValid() valid} attribute. - * - * @see MultipartRequestInputStream - */ - protected abstract void processStream() throws IOException; - - // ---------------------------------------------------------------- toString - - /** - * Returns basic information about the uploaded file. - */ - @Override - public String toString() { - return "FileUpload: uploaded=[" + isUploaded() + "] valid=[" + valid + "] field=[" + - header.getFormFieldName() + "] name=[" + header.getFileName() + "] size=[" + size + ']'; - } -} \ No newline at end of file diff --git a/jodd-core/src/main/java/jodd/io/upload/FileUploadFactory.java b/jodd-core/src/main/java/jodd/io/upload/FileUploadFactory.java deleted file mode 100644 index 02e4a3cea..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/FileUploadFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -/** - * {@link FileUpload} factory for handling uploaded files. Implementations may - * handle uploaded files differently: to store them to memory, directly to disk - * or something else. - */ -public interface FileUploadFactory { - - /** - * Creates new instance of {@link FileUpload uploaded file}. - */ - FileUpload create(MultipartRequestInputStream input); -} \ No newline at end of file diff --git a/jodd-core/src/main/java/jodd/io/upload/FileUploadHeader.java b/jodd-core/src/main/java/jodd/io/upload/FileUploadHeader.java deleted file mode 100644 index db8c5f6b1..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/FileUploadHeader.java +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -import jodd.io.FileNameUtil; -import jodd.util.StringPool; - -/** - * Parses file upload header. - */ -public class FileUploadHeader { - - String dataHeader; - String formFieldName; - - String formFileName; - String path; - String fileName; - - boolean isFile; - String contentType; - String mimeType; - String mimeSubtype; - String contentDisposition; - - - FileUploadHeader(final String dataHeader) { - this.dataHeader = dataHeader; - isFile = dataHeader.indexOf("filename") > 0; - formFieldName = getDataFieldValue(dataHeader, "name"); - if (isFile) { - formFileName = getDataFieldValue(dataHeader, "filename"); - if (formFileName == null) { - return; - } - if (formFileName.length() == 0) { - path = StringPool.EMPTY; - fileName = StringPool.EMPTY; - } - int ls = FileNameUtil.indexOfLastSeparator(formFileName); - if (ls == -1) { - path = StringPool.EMPTY; - fileName = formFileName; - } else { - path = formFileName.substring(0, ls); - fileName = formFileName.substring(ls + 1); - } - if (fileName.length() > 0) { - this.contentType = getContentType(dataHeader); - mimeType = getMimeType(contentType); - mimeSubtype = getMimeSubtype(contentType); - contentDisposition = getContentDisposition(dataHeader); - } - } - } - - // ---------------------------------------------------------------- utilities - - /** - * Gets value of data field or null if field not found. - */ - private String getDataFieldValue(final String dataHeader, final String fieldName) { - String value = null; - String token = String.valueOf((new StringBuffer(String.valueOf(fieldName))).append('=').append('"')); - int pos = dataHeader.indexOf(token); - if (pos > 0) { - int start = pos + token.length(); - int end = dataHeader.indexOf('"', start); - if ((start > 0) && (end > 0)) { - value = dataHeader.substring(start, end); - } - } - return value; - } - - /** - * Strips content type information from requests data header. - * @param dataHeader data header string - * @return content type or an empty string if no content type defined - */ - private String getContentType(final String dataHeader) { - String token = "Content-Type:"; - int start = dataHeader.indexOf(token); - if (start == -1) { - return StringPool.EMPTY; - } - start += token.length(); - return dataHeader.substring(start).trim(); - } - - private String getContentDisposition(final String dataHeader) { - int start = dataHeader.indexOf(':') + 1; - int end = dataHeader.indexOf(';'); - return dataHeader.substring(start, end); - } - - private String getMimeType(final String ContentType) { - int pos = ContentType.indexOf('/'); - if (pos == -1) { - return ContentType; - } - return ContentType.substring(1, pos); - } - - private String getMimeSubtype(final String ContentType) { - int start = ContentType.indexOf('/'); - if (start == -1) { - return ContentType; - } - start++; - return ContentType.substring(start); - } - - - // ---------------------------------------------------------------- public interface - - /** - * Returns true if uploaded data are correctly marked as a file. - * This is true if header contains string 'filename'. - */ - public boolean isFile() { - return isFile; - } - - /** - * Returns form field name. - */ - public String getFormFieldName() { - return formFieldName; - } - - /** - * Returns complete file name as specified at client side. - */ - public String getFormFilename() { - return formFileName; - } - - /** - * Returns file name (base name and extension, without full path data). - */ - public String getFileName() { - return fileName; - } - - /** - * Returns uploaded content type. It is usually in the following form:
- * mime_type/mime_subtype. - * - * @see #getMimeType() - * @see #getMimeSubtype() - */ - public String getContentType() { - return contentType; - } - - /** - * Returns file types MIME. - */ - public String getMimeType() { - return mimeType; - } - - /** - * Returns file sub type MIME. - */ - public String getMimeSubtype() { - return mimeSubtype; - } - - /** - * Returns content disposition. Usually it is 'form-data'. - */ - public String getContentDisposition() { - return contentDisposition; - } -} diff --git a/jodd-core/src/main/java/jodd/io/upload/MultipartRequestInputStream.java b/jodd-core/src/main/java/jodd/io/upload/MultipartRequestInputStream.java deleted file mode 100644 index ee1b4a9ea..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/MultipartRequestInputStream.java +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -import jodd.io.FastByteArrayOutputStream; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Extended input stream based on buffered requests input stream. - * It provides some more functions that might be useful when working - * with uploaded fies. - */ -public class MultipartRequestInputStream extends BufferedInputStream { - - public MultipartRequestInputStream(final InputStream in) { - super(in); - } - - /** - * Reads expected byte. Throws exception on streams end. - */ - public byte readByte() throws IOException { - int i = super.read(); - if (i == -1) { - throw new IOException("End of HTTP request stream reached"); - } - return (byte) i; - } - - /** - * Skips specified number of bytes. - */ - public void skipBytes(final int i) throws IOException { - long len = super.skip(i); - if (len != i) { - throw new IOException("Failed to skip data in HTTP request"); - } - } - - // ---------------------------------------------------------------- boundary - - protected byte[] boundary; - - /** - * Reads boundary from the input stream. - */ - public byte[] readBoundary() throws IOException { - FastByteArrayOutputStream boundaryOutput = new FastByteArrayOutputStream(); - byte b; - // skip optional whitespaces - while ((b = readByte()) <= ' ') { - } - boundaryOutput.write(b); - - // now read boundary chars - while ((b = readByte()) != '\r') { - boundaryOutput.write(b); - } - if (boundaryOutput.size() == 0) { - throw new IOException("Problems with parsing request: invalid boundary"); - } - skipBytes(1); - boundary = new byte[boundaryOutput.size() + 2]; - System.arraycopy(boundaryOutput.toByteArray(), 0, boundary, 2, boundary.length - 2); - boundary[0] = '\r'; - boundary[1] = '\n'; - return boundary; - } - - // ---------------------------------------------------------------- data header - - protected FileUploadHeader lastHeader; - - public FileUploadHeader getLastHeader() { - return lastHeader; - } - - /** - * Reads data header from the input stream. When there is no more - * headers (i.e. end of stream reached), returns null - */ - public FileUploadHeader readDataHeader(final String encoding) throws IOException { - String dataHeader = readDataHeaderString(encoding); - if (dataHeader != null) { - lastHeader = new FileUploadHeader(dataHeader); - } else { - lastHeader = null; - } - return lastHeader; - } - - - protected String readDataHeaderString(final String encoding) throws IOException { - FastByteArrayOutputStream data = new FastByteArrayOutputStream(); - byte b; - while (true) { - // end marker byte on offset +0 and +2 must be 13 - if ((b = readByte()) != '\r') { - data.write(b); - continue; - } - mark(4); - skipBytes(1); - int i = read(); - if (i == -1) { - // reached end of stream - return null; - } - if (i == '\r') { - reset(); - break; - } - reset(); - data.write(b); - } - skipBytes(3); - if (encoding != null) { - return data.toString(encoding); - } else { - return data.toString(); - } - } - - - // ---------------------------------------------------------------- copy - - /** - * Copies bytes from this stream to some output until boundary is - * reached. Returns number of copied bytes. It will throw an exception - * for any irregular behaviour. - */ - public int copyAll(final OutputStream out) throws IOException { - int count = 0; - while (true) { - byte b = readByte(); - if (isBoundary(b)) { - break; - } - out.write(b); - count++; - } - return count; - } - - /** - * Copies max or less number of bytes to output stream. Useful for determining - * if uploaded file is larger then expected. - */ - public int copyMax(final OutputStream out, final int maxBytes) throws IOException { - int count = 0; - while (true) { - byte b = readByte(); - if (isBoundary(b)) { - break; - } - out.write(b); - count++; - if (count == maxBytes) { - return count; - } - } - return count; - } - - /** - * Skips to the boundary and returns total number of bytes skipped. - */ - public int skipToBoundary() throws IOException { - int count = 0; - while (true) { - byte b = readByte(); - count++; - if (isBoundary(b)) { - break; - } - } - return count; - } - - /** - * Checks if the current byte (i.e. one that was read last) represents - * the very first byte of the boundary. - */ - public boolean isBoundary(byte b) throws IOException { - int boundaryLen = boundary.length; - mark(boundaryLen + 1); - int bpos = 0; - while (b == boundary[bpos]) { - b = readByte(); - bpos++; - if (bpos == boundaryLen) { - return true; // boundary found! - } - } - reset(); - return false; - } -} diff --git a/jodd-core/src/main/java/jodd/io/upload/MultipartStreamParser.java b/jodd-core/src/main/java/jodd/io/upload/MultipartStreamParser.java deleted file mode 100644 index 49410f932..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/MultipartStreamParser.java +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -import jodd.io.FastByteArrayOutputStream; -import jodd.io.upload.impl.MemoryFileUploadFactory; -import jodd.util.ArraysUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Generic, serlvets-free multipart request input stream parser. - */ -public class MultipartStreamParser { - - protected FileUploadFactory fileUploadFactory; - protected Map requestParameters; - protected Map requestFiles; - - public MultipartStreamParser() { - this(null); - } - - public MultipartStreamParser(final FileUploadFactory fileUploadFactory) { - this.fileUploadFactory = (fileUploadFactory == null ? new MemoryFileUploadFactory() : fileUploadFactory); - } - - private boolean parsed; - - /** - * Sets the loaded flag that indicates that input stream is loaded and parsed. - * Throws an exception if stream already loaded. - */ - protected void setParsed() throws IOException { - if (parsed) { - throw new IOException("Multi-part request already parsed"); - } - parsed = true; - } - - /** - * Returns true if multi-part request is already loaded. - */ - public boolean isParsed() { - return parsed; - } - - // ---------------------------------------------------------------- load and extract - - protected void putFile(final String name, final FileUpload value) { - if (requestFiles == null) { - requestFiles = new HashMap<>(); - } - - FileUpload[] fileUploads = requestFiles.get(name); - - if (fileUploads != null) { - fileUploads = ArraysUtil.append(fileUploads, value); - } else { - fileUploads = new FileUpload[] {value}; - } - - requestFiles.put(name, fileUploads); - } - - protected void putParameters(final String name, final String[] values) { - if (requestParameters == null) { - requestParameters = new HashMap<>(); - } - requestParameters.put(name, values); - } - - protected void putParameter(final String name, final String value) { - if (requestParameters == null) { - requestParameters = new HashMap<>(); - } - - String[] params = requestParameters.get(name); - - if (params != null) { - params = ArraysUtil.append(params, value); - } else { - params = new String[] {value}; - } - - requestParameters.put(name, params); - } - - /** - * Extracts uploaded files and parameters from the request data. - */ - public void parseRequestStream(final InputStream inputStream, final String encoding) throws IOException { - setParsed(); - - MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream); - input.readBoundary(); - while (true) { - FileUploadHeader header = input.readDataHeader(encoding); - if (header == null) { - break; - } - - if (header.isFile) { - String fileName = header.fileName; - if (fileName.length() > 0) { - if (header.contentType.indexOf("application/x-macbinary") > 0) { - input.skipBytes(128); - } - } - FileUpload newFile = fileUploadFactory.create(input); - newFile.processStream(); - if (fileName.length() == 0) { - // file was specified, but no name was provided, therefore it was not uploaded - if (newFile.getSize() == 0) { - newFile.size = -1; - } - } - putFile(header.formFieldName, newFile); - } else { - // no file, therefore it is regular form parameter. - FastByteArrayOutputStream fbos = new FastByteArrayOutputStream(); - input.copyAll(fbos); - String value = encoding != null ? new String(fbos.toByteArray(), encoding) : new String(fbos.toByteArray()); - putParameter(header.formFieldName, value); - } - - input.skipBytes(1); - input.mark(1); - - // read byte, but may be end of stream - int nextByte = input.read(); - if (nextByte == -1 || nextByte == '-') { - input.reset(); - break; - } - input.reset(); - } - } - - // ---------------------------------------------------------------- parameters - - - /** - * Returns single value of a parameter. If parameter name is used for - * more then one parameter, only the first one will be returned. - * - * @return parameter value, or null if not found - */ - public String getParameter(final String paramName) { - if (requestParameters == null) { - return null; - } - String[] values = requestParameters.get(paramName); - if ((values != null) && (values.length > 0)) { - return values[0]; - } - return null; - } - - /** - * Returns the names of the parameters contained in this request. - */ - public Set getParameterNames() { - if (requestParameters == null) { - return Collections.emptySet(); - } - return requestParameters.keySet(); - } - - /** - * Returns all values all of the values the given request parameter has. - */ - public String[] getParameterValues(final String paramName) { - if (requestParameters == null) { - return null; - } - return requestParameters.get(paramName); - } - - - /** - * Returns uploaded file. - * @param paramName parameter name of the uploaded file - * @return uploaded file or null if parameter name not found - */ - public FileUpload getFile(final String paramName) { - if (requestFiles == null) { - return null; - } - FileUpload[] values = requestFiles.get(paramName); - if ((values != null) && (values.length > 0)) { - return values[0]; - } - return null; - } - - - /** - * Returns all uploaded files the given request parameter has. - */ - public FileUpload[] getFiles(final String paramName) { - if (requestFiles == null) { - return null; - } - return requestFiles.get(paramName); - } - - /** - * Returns parameter names of all uploaded files. - */ - public Set getFileParameterNames() { - if (requestFiles == null) { - return Collections.emptySet(); - } - return requestFiles.keySet(); - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUpload.java b/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUpload.java deleted file mode 100644 index f59d8aa49..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUpload.java +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.core.JoddCore; -import jodd.io.FastByteArrayOutputStream; -import jodd.io.FileNameUtil; -import jodd.io.FileUtil; -import jodd.io.IOUtil; -import jodd.io.upload.FileUpload; -import jodd.io.upload.MultipartRequestInputStream; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Smart {@link FileUpload} implementation that defer the action of what to do with uploaded file - * for later. Internally, it stores uploaded file either in memory if it is small, or, in all - * other cases, it stores them in TEMP folder. - */ -public class AdaptiveFileUpload extends FileUpload { - - protected static final String TMP_FILE_SUFFIX = ".upload.tmp"; - - protected final int memoryThreshold; - protected final File uploadPath; - protected final boolean breakOnError; - protected final String[] fileExtensions; - protected final boolean allowFileExtensions; - - AdaptiveFileUpload(final MultipartRequestInputStream input, final int memoryThreshold, final File uploadPath, final int maxFileSize, final boolean breakOnError, final String[] extensions, final boolean allowed) { - super(input, maxFileSize); - this.memoryThreshold = memoryThreshold; - this.uploadPath = uploadPath; - this.breakOnError = breakOnError; - this.fileExtensions = extensions; - this.allowFileExtensions = allowed; - } - - // ---------------------------------------------------------------- settings - - public int getMemoryThreshold() { - return memoryThreshold; - } - - public File getUploadPath() { - return uploadPath; - } - - public boolean isBreakOnError() { - return breakOnError; - } - - public String[] getFileExtensions() { - return fileExtensions; - } - - public boolean isAllowFileExtensions() { - return allowFileExtensions; - } - - // ---------------------------------------------------------------- properties - - protected File tempFile; - protected byte[] data; - - /** - * Returns true if file upload resides in memory. - */ - @Override - public boolean isInMemory() { - return data != null; - } - - // ---------------------------------------------------------------- process - - - protected boolean matchFileExtension() throws IOException { - final String fileNameExtension = FileNameUtil.getExtension(getHeader().getFileName()); - for (final String fileExtension : fileExtensions) { - if (fileNameExtension.equalsIgnoreCase(fileExtension)) { - if (!allowFileExtensions) { // extension matched and it is not allowed - if (breakOnError) { - throw new IOException("Upload filename extension not allowed: " + fileNameExtension); - } - size = input.skipToBoundary(); - return false; - } - return true; // extension matched and it is allowed. - } - } - if (allowFileExtensions) { // extension is not one of the allowed ones. - if (breakOnError) { - throw new IOException("Upload filename extension not allowed: " + fileNameExtension); - } - size = input.skipToBoundary(); - return false; - } - return true; - } - - /** - * Determines if upload is allowed. - */ - protected boolean checkUpload() throws IOException { - if (fileExtensions != null) { - if (!matchFileExtension()) { - return false; - } - } - return true; - } - - @Override - protected void processStream() throws IOException { - if (!checkUpload()) { - return; - } - size = 0; - if (memoryThreshold > 0) { - final FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(memoryThreshold + 1); - final int written = input.copyMax(fbaos, memoryThreshold + 1); - data = fbaos.toByteArray(); - if (written <= memoryThreshold) { - size = data.length; - valid = true; - return; - } - } - - tempFile = FileUtil.createTempFile(JoddCore.tempFilePrefix, TMP_FILE_SUFFIX, uploadPath); - final BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile)); - if (data != null) { - size = data.length; - out.write(data); - data = null; // not needed anymore - } - boolean deleteTempFile = false; - try { - if (maxFileSize == -1) { - size += input.copyAll(out); - } else { - size += input.copyMax(out, maxFileSize - size + 1); // one more byte to detect larger files - if (size > maxFileSize) { - deleteTempFile = true; - fileTooBig = true; - valid = false; - if (breakOnError) { - throw new IOException("File upload (" + header.getFileName() + ") too big, > " + maxFileSize); - } - input.skipToBoundary(); - return; - } - } - valid = true; - } finally { - IOUtil.close(out); - if (deleteTempFile) { - tempFile.delete(); - tempFile = null; - } - } - } - - // ---------------------------------------------------------------- operations - - - /** - * Deletes file uploaded item from disk or memory. - */ - public void delete() { - if (tempFile != null) { - tempFile.delete(); - } - if (data != null) { - data = null; - } - } - - /** - * Writes file uploaded item. - */ - public File write(final String destination) throws IOException { - return write(new File(destination)); - } - - /** - * Writes file upload item to destination folder or to destination file. - * Returns the destination file. - */ - public File write(File destination) throws IOException { - if (destination.isDirectory()) { - destination = new File(destination, this.header.getFileName()); - } - if (data != null) { - FileUtil.writeBytes(destination, data); - } else { - if (tempFile != null) { - FileUtil.move(tempFile, destination); - } - } - return destination; - } - - /** - * Returns the content of file upload item. - */ - @Override - public byte[] getFileContent() throws IOException { - if (data != null) { - return data; - } - if (tempFile != null) { - return FileUtil.readBytes(tempFile); - } - return null; - } - - @Override - public InputStream getFileInputStream() throws IOException { - if (data != null) { - return new BufferedInputStream(new ByteArrayInputStream(data)); - } - if (tempFile != null) { - return new BufferedInputStream(new FileInputStream(tempFile)); - } - return null; - } - - - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUploadFactory.java b/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUploadFactory.java deleted file mode 100644 index 495f43395..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/AdaptiveFileUploadFactory.java +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.io.upload.FileUpload; -import jodd.io.upload.FileUploadFactory; -import jodd.io.upload.MultipartRequestInputStream; - -import java.io.File; - -/** - * - * Factory for {@link AdaptiveFileUpload}. - */ -public class AdaptiveFileUploadFactory implements FileUploadFactory { - - protected int memoryThreshold = 8192; - protected File uploadPath; - protected int maxFileSize = 102400; - protected boolean breakOnError; - protected String[] fileExtensions; - protected boolean allowFileExtensions = true; - - /** - * {@inheritDoc} - */ - @Override - public FileUpload create(final MultipartRequestInputStream input) { - return new AdaptiveFileUpload(input, memoryThreshold, uploadPath, maxFileSize, breakOnError, fileExtensions, allowFileExtensions); - } - - // ---------------------------------------------------------------- properties - - public int getMemoryThreshold() { - return memoryThreshold; - } - /** - * Specifies per file memory limit for keeping uploaded files in the memory. - */ - public AdaptiveFileUploadFactory setMemoryThreshold(final int memoryThreshold) { - if (memoryThreshold >= 0) { - this.memoryThreshold = memoryThreshold; - } - return this; - } - - public File getUploadPath() { - return uploadPath; - } - - /** - * Specifies the upload path. If set to null default - * system TEMP path will be used. - */ - public AdaptiveFileUploadFactory setUploadPath(final File uploadPath) { - this.uploadPath = uploadPath; - return this; - } - - public int getMaxFileSize() { - return maxFileSize; - } - - /** - * Sets maximum file upload size. Setting to -1 - * disables this constraint. - */ - public AdaptiveFileUploadFactory setMaxFileSize(final int maxFileSize) { - this.maxFileSize = maxFileSize; - return this; - } - - public boolean isBreakOnError() { - return breakOnError; - } - - public AdaptiveFileUploadFactory setBreakOnError(final boolean breakOnError) { - this.breakOnError = breakOnError; - return this; - } - - /** - * Specifies if upload should break on error. - */ - public AdaptiveFileUploadFactory breakOnError(final boolean breakOnError) { - this.breakOnError = breakOnError; - return this; - } - - /** - * Allow or disallow set of file extensions. Only one rule can be active at time, - * which means user can only specify extensions that are either allowed or disallowed. - * Setting this value to null will turn this feature off. - */ - public AdaptiveFileUploadFactory setFileExtensions(final String[] fileExtensions, final boolean allow) { - this.fileExtensions = fileExtensions; - this.allowFileExtensions = allow; - return this; - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUpload.java b/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUpload.java deleted file mode 100644 index 614881eda..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUpload.java +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.io.FileUtil; -import jodd.io.IOUtil; -import jodd.io.upload.FileUpload; -import jodd.io.upload.MultipartRequestInputStream; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * {@link FileUpload} that saves uploaded files directly to destination folder. - */ -public class DiskFileUpload extends FileUpload { - - protected final File destFolder; - - DiskFileUpload(final MultipartRequestInputStream input, final File destinationFolder, final int maxFileSize) { - super(input, maxFileSize); - this.destFolder = destinationFolder; - } - - /** - * Returns false as uploaded file is stored on disk. - */ - @Override - public boolean isInMemory() { - return false; - } - - /** - * Returns destination folder. - */ - public File getDestinationFolder() { - return destFolder; - } - - /** - * Returns uploaded and saved file. - */ - public File getFile() { - return file; - } - - protected File file; - - /** - * Returns files content from disk file. - * If error occurs, it returns null - */ - @Override - public byte[] getFileContent() throws IOException { - return FileUtil.readBytes(file); - } - - /** - * Returns new buffered file input stream. - */ - @Override - public InputStream getFileInputStream() throws IOException { - return new BufferedInputStream(new FileInputStream(file)); - } - - @Override - protected void processStream() throws IOException { - file = new File(destFolder, header.getFileName()); - final OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); - - size = 0; - try { - if (maxFileSize == -1) { - size = input.copyAll(out); - } else { - size = input.copyMax(out, maxFileSize + 1); // one more byte to detect larger files - if (size > maxFileSize) { - fileTooBig = true; - valid = false; - input.skipToBoundary(); - return; - } - } - valid = true; - } finally { - IOUtil.close(out); - } - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUploadFactory.java b/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUploadFactory.java deleted file mode 100644 index c20bb84dc..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/DiskFileUploadFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.io.upload.FileUpload; -import jodd.io.upload.FileUploadFactory; -import jodd.io.upload.MultipartRequestInputStream; -import jodd.util.SystemUtil; - -import java.io.File; -import java.io.IOException; - -/** - * Factory for {@link jodd.io.upload.impl.DiskFileUpload} - */ -public class DiskFileUploadFactory implements FileUploadFactory { - - protected File destFolder; - - protected int maxFileSize = 102400; - - public DiskFileUploadFactory() throws IOException { - this(SystemUtil.info().getTempDir()); - } - - public DiskFileUploadFactory(final String destFolder) throws IOException { - this(destFolder, 102400); - - } - - public DiskFileUploadFactory(final String destFolder, final int maxFileSize) throws IOException { - setUploadDir(destFolder); - this.maxFileSize = maxFileSize; - } - - - public DiskFileUploadFactory setUploadDir(String destFolder) throws IOException { - if (destFolder == null) { - destFolder = SystemUtil.info().getTempDir(); - } - final File destination = new File(destFolder); - if (!destination.exists()) { - destination.mkdirs(); - } - if (!destination.isDirectory()) { - throw new IOException("Invalid destination folder: " + destFolder); - } - this.destFolder = destination; - return this; - } - - public int getMaxFileSize() { - return maxFileSize; - } - - /** - * Sets maximum file upload size. Setting to -1 will disable this constraint. - */ - public DiskFileUploadFactory setMaxFileSize(final int maxFileSize) { - this.maxFileSize = maxFileSize; - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public FileUpload create(final MultipartRequestInputStream input) { - return new DiskFileUpload(input, destFolder, maxFileSize); - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUpload.java b/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUpload.java deleted file mode 100644 index 73be13b6e..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUpload.java +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.io.FastByteArrayOutputStream; -import jodd.io.upload.FileUpload; -import jodd.io.upload.MultipartRequestInputStream; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * {@link FileUpload} that stores uploaded files in memory byte array. - */ -public class MemoryFileUpload extends FileUpload { - - MemoryFileUpload(final MultipartRequestInputStream input, final int maxFileSize) { - super(input, maxFileSize); - } - - // ---------------------------------------------------------------- logic - - protected byte[] data; - - /** - * Returns byte array containing uploaded file data. - */ - @Override - public byte[] getFileContent() { - return data; - } - - /** - * Returns true as uploaded file is stored in memory. - */ - @Override - public boolean isInMemory() { - return true; - } - - /** - * Returns byte array input stream. - */ - @Override - public InputStream getFileInputStream() { - return new ByteArrayInputStream(data); - } - - /** - * Reads data from input stream into byte array and stores file size. - */ - @Override - public void processStream() throws IOException { - final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - size = 0; - if (maxFileSize == -1) { - size += input.copyAll(out); - } else { - size += input.copyMax(out, maxFileSize + 1); // one more byte to detect larger files - if (size > maxFileSize) { - fileTooBig = true; - valid = false; - input.skipToBoundary(); - return; - } - } - data = out.toByteArray(); - size = data.length; - valid = true; - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUploadFactory.java b/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUploadFactory.java deleted file mode 100644 index de8fe437d..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/MemoryFileUploadFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload.impl; - -import jodd.io.upload.FileUpload; -import jodd.io.upload.FileUploadFactory; -import jodd.io.upload.MultipartRequestInputStream; - -/** - * Factory for {@link jodd.io.upload.impl.MemoryFileUpload}. - */ -public class MemoryFileUploadFactory implements FileUploadFactory { - - protected int maxFileSize = 102400; - - public int getMaxFileSize() { - return maxFileSize; - } - - /** - * Sets maximum file upload size. Setting to -1 will disable this constraint. - */ - public MemoryFileUploadFactory setMaxFileSize(final int maxFileSize) { - this.maxFileSize = maxFileSize; - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public FileUpload create(final MultipartRequestInputStream input) { - return new MemoryFileUpload(input, maxFileSize); - } - -} diff --git a/jodd-core/src/main/java/jodd/io/upload/impl/package-info.java b/jodd-core/src/main/java/jodd/io/upload/impl/package-info.java deleted file mode 100644 index d8bccc80b..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/impl/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Various implementations of uploaded files and their factories. - */ -package jodd.io.upload.impl; \ No newline at end of file diff --git a/jodd-core/src/main/java/jodd/io/upload/package-info.java b/jodd-core/src/main/java/jodd/io/upload/package-info.java deleted file mode 100644 index cc252c3b9..000000000 --- a/jodd-core/src/main/java/jodd/io/upload/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Multipart streams and file uploads. - */ -package jodd.io.upload; \ No newline at end of file diff --git a/jodd-core/src/test/java/jodd/io/upload/FileUploadTest.java b/jodd-core/src/test/java/jodd/io/upload/FileUploadTest.java deleted file mode 100644 index 9084a795a..000000000 --- a/jodd-core/src/test/java/jodd/io/upload/FileUploadTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.io.upload; - -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URL; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class FileUploadTest { - - @Test - void testFileNames() throws IOException { - URL data = FileUploadTest.class.getResource("upload.txt"); - String file = data.getFile(); - - MultipartStreamParser msp = new MultipartStreamParser(); - msp.parseRequestStream(new FileInputStream(new File(file)), "ISO-8859-1"); - - FileUpload fu = msp.getFile("avatar"); - assertEquals("smiley-cool.png", fu.getHeader().getFileName()); - - fu = msp.getFile("attach1"); - assertEquals("file1.txt", fu.getHeader().getFileName()); - - fu = msp.getFile("attach2"); - assertEquals("file2.txt", fu.getHeader().getFileName()); - } -} diff --git a/jodd-db/build.gradle b/jodd-db/build.gradle index beb89e162..7472f9276 100755 --- a/jodd-db/build.gradle +++ b/jodd-db/build.gradle @@ -7,7 +7,7 @@ apply plugin: 'kotlin' dependencies { api project(':jodd-core') api project(':jodd-jtx') - api project(':jodd-props') + api 'org.jodd:jodd-props:6.0.1' api 'org.slf4j:slf4j-api:1.7.30' testImplementation lib.junit5 diff --git a/jodd-db/src/main/java/jodd/db/querymap/DbPropsQueryMap.java b/jodd-db/src/main/java/jodd/db/querymap/DbPropsQueryMap.java index 7a585336d..47ee33487 100644 --- a/jodd-db/src/main/java/jodd/db/querymap/DbPropsQueryMap.java +++ b/jodd-db/src/main/java/jodd/db/querymap/DbPropsQueryMap.java @@ -25,7 +25,13 @@ package jodd.db.querymap; +import jodd.core.JoddCore; +import jodd.exception.UncheckedException; +import jodd.io.findfile.ClassScanner; import jodd.props.Props; +import jodd.util.StringUtil; + +import java.nio.charset.StandardCharsets; /** * {@link jodd.db.querymap.QueryMap} implementation based on @@ -57,7 +63,7 @@ public Props props() { @Override public void reload() { props = new Props(); - props.loadFromClasspath(patterns); + loadFromClasspath(props, patterns); } @Override @@ -75,4 +81,24 @@ public int size() { public String getQuery(final String key) { return props.getValue(key); } -} \ No newline at end of file + + private void loadFromClasspath(final Props props, final String... patterns) { + ClassScanner.create() + .registerEntryConsumer(entryData -> { + String usedEncoding = JoddCore.encoding; + if (StringUtil.endsWithIgnoreCase(entryData.name(), ".properties")) { + usedEncoding = StandardCharsets.ISO_8859_1.name(); + } + + final String encoding = usedEncoding; + UncheckedException.runAndWrapException(() -> props.load(entryData.openInputStream(), encoding)); + }) + .includeResources(true) + .ignoreException(true) + .excludeCommonJars() + .excludeAllEntries(true) + .includeEntries(patterns) + .scanDefaultClasspath() + .start(); + } +} diff --git a/jodd-decora/build.gradle b/jodd-decora/build.gradle index 4e36e2f52..5f86ce181 100644 --- a/jodd-decora/build.gradle +++ b/jodd-decora/build.gradle @@ -6,7 +6,7 @@ dependencies { api project(':jodd-servlet') api 'org.slf4j:slf4j-api:1.7.30' - api 'org.jodd:jodd-lagarto:6.0.1' + api 'org.jodd:jodd-lagarto:6.0.2' provided lib.servlet diff --git a/jodd-htmlstapler/build.gradle b/jodd-htmlstapler/build.gradle index fb3de66fc..5878c3eea 100644 --- a/jodd-htmlstapler/build.gradle +++ b/jodd-htmlstapler/build.gradle @@ -7,7 +7,7 @@ dependencies { api project(':jodd-servlet') api 'org.slf4j:slf4j-api:1.7.30' - api 'org.jodd:jodd-lagarto:6.0.1' + api 'org.jodd:jodd-lagarto:6.0.2' provided lib.servlet provided lib.jsp diff --git a/jodd-http b/jodd-http new file mode 160000 index 000000000..38bba4103 --- /dev/null +++ b/jodd-http @@ -0,0 +1 @@ +Subproject commit 38bba41031e27c21e0baed9c24c0a8e56d2c3c75 diff --git a/jodd-http/build.gradle b/jodd-http/build.gradle deleted file mode 100644 index 72bbab9fa..000000000 --- a/jodd-http/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ - -ext.moduleName = 'Jodd HTTP client' -ext.moduleDescription = 'Jodd HTTP client is easy-to-use http client.' - -dependencies { - api project(':jodd-core') - - testImplementation lib.junit5 - testImplementation lib.tomcat_embed - testImplementation lib.mockserver -} diff --git a/jodd-http/src/main/java/jodd/http/Buffer.java b/jodd-http/src/main/java/jodd/http/Buffer.java deleted file mode 100644 index b58a05169..000000000 --- a/jodd-http/src/main/java/jodd/http/Buffer.java +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.buffer.FastByteBuffer; -import jodd.http.up.Uploadable; -import jodd.io.IOUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.LinkedList; - -/** - * Holds request/response content until it is actually send. - * File content (i.e. {@link jodd.http.up.Uploadable}) is - * not read until it is really used. - */ -public class Buffer { - - protected LinkedList list = new LinkedList<>(); - protected FastByteBuffer last; - protected int size; - - /** - * Appends string content to buffer. - */ - public Buffer append(final String string) { - ensureLast(); - - final byte[] bytes = string.getBytes(StandardCharsets.ISO_8859_1); - last.append(bytes); - size += bytes.length; - - return this; - } - - /** - * Appends a char. - */ - public Buffer append(final char c) { - append(Character.toString(c)); - return this; - } - - /** - * Appends a number. - */ - public Buffer append(final int number) { - append(Integer.toString(number)); - return this; - } - - /** - * Appends {@link jodd.http.up.Uploadable} to buffer. - */ - public Buffer append(final Uploadable uploadable) { - list.add(uploadable); - size += uploadable.getSize(); - last = null; - return this; - } - - /** - * Appends other buffer to this one. - */ - public Buffer append(final Buffer buffer) { - if (buffer.list.isEmpty()) { - // nothing to append - return buffer; - } - list.addAll(buffer.list); - last = buffer.last; - size += buffer.size; - return this; - } - - /** - * Returns buffer size. - */ - public int size() { - return size; - } - - /** - * Ensures that last buffer exist. - */ - private void ensureLast() { - if (last == null) { - last = new FastByteBuffer(); - list.add(last); - } - } - - // ---------------------------------------------------------------- write - - /** - * Writes content to the writer. - */ - public void writeTo(final Writer writer) throws IOException { - for (final Object o : list) { - if (o instanceof FastByteBuffer) { - final FastByteBuffer fastByteBuffer = (FastByteBuffer) o; - - final byte[] array = fastByteBuffer.toArray(); - - writer.write(new String(array, StandardCharsets.ISO_8859_1)); - } - else if (o instanceof Uploadable) { - final Uploadable uploadable = (Uploadable) o; - - final InputStream inputStream = uploadable.openInputStream(); - - try { - IOUtil.copy(inputStream, writer, StandardCharsets.ISO_8859_1); - } - finally { - IOUtil.close(inputStream); - } - } - } - } - - /** - * Writes content to the output stream. - */ - public void writeTo(final OutputStream out) throws IOException { - for (final Object o : list) { - if (o instanceof FastByteBuffer) { - final FastByteBuffer fastByteBuffer = (FastByteBuffer) o; - - out.write(fastByteBuffer.toArray()); - } - else if (o instanceof Uploadable) { - final Uploadable uploadable = (Uploadable) o; - - final InputStream inputStream = uploadable.openInputStream(); - - try { - IOUtil.copy(inputStream, out); - } - finally { - IOUtil.close(inputStream); - } - } - } - } - - /** - * Writes content to the output stream, using progress listener to track the sending progress. - */ - public void writeTo(final OutputStream out, final HttpProgressListener progressListener) throws IOException { - - // start - - final int size = size(); - final int callbackSize = progressListener.callbackSize(size); - int count = 0; // total count - int step = 0; // step is offset in current chunk - - progressListener.transferred(count); - - // loop - - for (final Object o : list) { - if (o instanceof FastByteBuffer) { - final FastByteBuffer fastByteBuffer = (FastByteBuffer) o; - final byte[] bytes = fastByteBuffer.toArray(); - - int offset = 0; - - while (offset < bytes.length) { - // calc the remaining sending chunk size - int chunk = callbackSize - step; - - // check if this chunk size fits the bytes array - if (offset + chunk > bytes.length) { - chunk = bytes.length - offset; - } - - // writes the chunk - out.write(bytes, offset, chunk); - - offset += chunk; - step += chunk; - count += chunk; - - // listener - if (step >= callbackSize) { - progressListener.transferred(count); - step -= callbackSize; - } - } - } - else if (o instanceof Uploadable) { - final Uploadable uploadable = (Uploadable) o; - - final InputStream inputStream = uploadable.openInputStream(); - - int remaining = uploadable.getSize(); - - try { - while (remaining > 0) { - // calc the remaining sending chunk size - int chunk = callbackSize - step; - - // check if this chunk size fits the remaining size - if (chunk > remaining) { - chunk = remaining; - } - - // writes remaining chunk - IOUtil.copy(inputStream, out, chunk); - - remaining -= chunk; - step += chunk; - count += chunk; - - // listener - if (step >= callbackSize) { - progressListener.transferred(count); - step -= callbackSize; - } - } - } - finally { - IOUtil.close(inputStream); - } - } - } - - // end - - if (step != 0) { - progressListener.transferred(count); - } - } - -} diff --git a/jodd-http/src/main/java/jodd/http/Cookie.java b/jodd-http/src/main/java/jodd/http/Cookie.java deleted file mode 100644 index 465408604..000000000 --- a/jodd-http/src/main/java/jodd/http/Cookie.java +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.util.StringUtil; - -/** - * Cookie object. Simple cookie data holder, cookie header parser and generator. - */ -public class Cookie { - - // the value of the cookie itself - - private String name; - private String value; - - // attributes encoded in the header's cookie fields - - private String comment; // ;Comment=VALUE ... describes cookie's use - // ;Discard ... implied by maxAge < 0 - private String domain; // ;Domain=VALUE ... domain that sees cookie - private Integer maxAge; // ;Max-Age=VALUE ... cookies auto-expire - private String expires; // ;Expires= ... expires values - private String path; // ;Path=VALUE ... URLs that see the cookie - private boolean secure; // ;Secure ... e.g. use SSL - private Integer version; // ;Version=1 ... means RFC 2109++ style - private boolean httpOnly; // ;HttpOnly - - - /** - * Creates cookie with specified name and value. - *

- * The name must conform to RFC 2109. That means it can contain - * only ASCII alphanumeric characters and cannot contain commas, - * semicolons, or white space or begin with a $ character. - *

- * The value can be anything the server chooses to send. - */ - public Cookie(final String name, final String value) { - setName(name); - setValue(value); - } - - /** - * Parses cookie data from given user-agent string. - */ - public Cookie(String cookie) { - int from = 0; - int ndx = 0; - - cookie = cookie.trim(); - - while (ndx < cookie.length()) { - ndx = cookie.indexOf(';', from); - - if (ndx == -1) { - // last chunk - ndx = cookie.length(); - } - int ndx2 = cookie.indexOf('=', from); - - String name; - String value; - if (ndx2 != -1 && ndx2 < ndx) { - name = cookie.substring(from, ndx2).trim(); - value = cookie.substring(ndx2 + 1, ndx).trim(); - } else { - if (from == ndx) { - ndx++; - continue; - } - name = cookie.substring(from, ndx).trim(); - value = null; - } - - if (value != null && name.equalsIgnoreCase("Max-Age")) { - setMaxAge(Integer.parseInt(value)); - } else if (name.equalsIgnoreCase("Comment")) { - setComment(value); - } else if (name.equalsIgnoreCase("Domain")) { - setDomain(value); - } else if (name.equalsIgnoreCase("Path")) { - setPath(value); - } else if (name.equalsIgnoreCase("Secure")) { - setSecure(true); - } else if (value != null && name.equalsIgnoreCase("Version")) { - setVersion(Integer.parseInt(value)); - } else if (name.equalsIgnoreCase("HttpOnly")) { - setHttpOnly(true); - } else if (name.equalsIgnoreCase("Expires")) { - setExpires(value); - } else if (this.name == null && !StringUtil.isBlank(name)) { - setName(name); - setValue(value); - } - - // continue - from = ndx + 1; - } - } - - /** - * Sets the cookie name and checks for validity. - */ - private void setName(final String name) { - if (name.contains(";") || name.contains(",") || name.startsWith("$")) { - throw new IllegalArgumentException("Invalid cookie name:" + name); - } - - for (int n = 0; n < name.length(); n++) { - char c = name.charAt(n); - if (c <= 0x20 || c >= 0x7f) { - throw new IllegalArgumentException("Invalid cookie name:" + name); - } - } - this.name = name; - } - - /** - * Returns the comment describing the purpose of this cookie, or - * null if the cookie has no comment. - */ - public String getComment() { - return comment; - } - - /** - * Specifies a comment that describes a cookie's purpose. - * The comment is useful if the browser presents the cookie - * to the user. - */ - public Cookie setComment(final String purpose) { - comment = purpose; - return this; - } - - /** - * Returns the domain name set for this cookie. The form of - * the domain name is set by RFC 2109. - */ - - public String getDomain() { - return domain; - } - - /** - * Specifies the domain within which this cookie should be presented. - *

- * The form of the domain name is specified by RFC 2109. A domain - * name begins with a dot (.foo.com) and means that - * the cookie is visible to servers in a specified Domain Name System - * (DNS) zone (for example, www.foo.com, but not - * a.b.foo.com). By default, cookies are only returned - * to the server that sent them. - */ - - public Cookie setDomain(final String pattern) { - domain = pattern.toLowerCase(); // IE allegedly needs this - return this; - } - - /** - * Returns the maximum age of the cookie, specified in seconds, - * By default, -1 indicating the cookie will persist - * until browser shutdown. - */ - - public Integer getMaxAge() { - return maxAge; - } - - /** - * Sets the maximum age of the cookie in seconds. - *

- * A positive value indicates that the cookie will expire - * after that many seconds have passed. Note that the value is - * the maximum age when the cookie will expire, not the cookie's - * current age. - *

- * A negative value means - * that the cookie is not stored persistently and will be deleted - * when the Web browser exits. A zero value causes the cookie - * to be deleted. - */ - - public Cookie setMaxAge(final int expiry) { - maxAge = Integer.valueOf(expiry); - return this; - } - - /** - * Returns the path on the server - * to which the browser returns this cookie. The - * cookie is visible to all subpaths on the server. - */ - public String getPath() { - return path; - } - - /** - * Specifies a path for the cookie - * to which the client should return the cookie. - *

- * The cookie is visible to all the pages in the directory - * you specify, and all the pages in that directory's subdirectories. - * A cookie's path must include the servlet that set the cookie, - * for example, /catalog, which makes the cookie - * visible to all directories on the server under /catalog. - * - *

Consult RFC 2109 (available on the Internet) for more - * information on setting path names for cookies. - */ - public Cookie setPath(final String uri) { - path = uri; - return this; - } - - /** - * Returns true if the browser is sending cookies - * only over a secure protocol, or false if the - * browser can send cookies using any protocol. - */ - public boolean isSecure() { - return secure; - } - - /** - * Indicates to the browser whether the cookie should only be sent - * using a secure protocol, such as HTTPS or SSL. - */ - public Cookie setSecure(final boolean flag) { - secure = flag; - return this; - } - - /** - * Returns the name of the cookie. The name cannot be changed after - * creation. - */ - public String getName() { - return name; - } - - /** - * Returns the value of the cookie. - */ - public String getValue() { - return value; - } - - /** - * Assigns a new value to a cookie after the cookie is created. - * If you use a binary value, you may want to use BASE64 encoding. - */ - public Cookie setValue(final String newValue) { - value = newValue; - return this; - } - - /** - * Returns the version of the protocol this cookie complies - * with. Version 1 complies with RFC 2109, - * and version 0 complies with the original - * cookie specification drafted by Netscape. Cookies provided - * by a browser use and identify the browser's cookie version. - */ - public Integer getVersion() { - return version; - } - - /** - * Sets the version of the cookie protocol this cookie complies - * with. Version 0 complies with the original Netscape cookie - * specification. Version 1 complies with RFC 2109. - */ - public Cookie setVersion(final int version) { - this.version = Integer.valueOf(version); - return this; - } - - public boolean isHttpOnly() { - return httpOnly; - } - - public Cookie setHttpOnly(final boolean httpOnly) { - this.httpOnly = httpOnly; - return this; - } - - public String getExpires() { - return expires; - } - - public Cookie setExpires(final String expires) { - this.expires = expires; - return this; - } - - @Override - public String toString() { - StringBuilder cookie = new StringBuilder(); - - cookie.append(name).append('=').append(value); - - if (maxAge != null) { - cookie.append("; Max-Age=").append(maxAge); - } - if (expires != null) { - cookie.append("; Expires=").append(expires); - } - if (comment != null) { - cookie.append("; Comment=").append(comment); - } - if (domain != null) { - cookie.append("; Domain=").append(domain); - } - if (path != null) { - cookie.append("; Path=").append(path); - } - if (secure) { - cookie.append("; Secure"); - } - if (version != null) { - cookie.append("; Version=").append(version); - } - if (httpOnly) { - cookie.append("; HttpOnly"); - } - - return cookie.toString(); - } - -} diff --git a/jodd-http/src/main/java/jodd/http/HeadersMultiMap.java b/jodd-http/src/main/java/jodd/http/HeadersMultiMap.java deleted file mode 100644 index 6ae0b8119..000000000 --- a/jodd-http/src/main/java/jodd/http/HeadersMultiMap.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import java.util.List; - -public class HeadersMultiMap extends HttpMultiMap { - - protected HeadersMultiMap() { - super(false); - } - - /** - * Adds new header value. If existing value exist, it will be removed - * so the store the new key value. - */ - public void addHeader(final String name, final String value) { - List valuesList = super.getAll(name); - if (valuesList.isEmpty()) { - super.add(name, value); - return; - } - super.remove(name); - valuesList.add(value); - super.addAll(name, valuesList); - } - - public void setHeader(final String name, final String value) { - super.set(name, value); - } - - public String getHeader(final String name) { - return super.get(name); - } -} diff --git a/jodd-http/src/main/java/jodd/http/HttpBase.java b/jodd-http/src/main/java/jodd/http/HttpBase.java deleted file mode 100644 index 9af4448c4..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpBase.java +++ /dev/null @@ -1,1099 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.up.ByteArrayUploadable; -import jodd.http.up.FileUploadable; -import jodd.http.up.Uploadable; -import jodd.io.FastCharArrayWriter; -import jodd.io.FileNameUtil; -import jodd.io.IOUtil; -import jodd.io.upload.FileUpload; -import jodd.io.upload.MultipartStreamParser; -import jodd.net.MimeTypes; -import jodd.time.TimeUtil; -import jodd.util.RandomString; -import jodd.util.StringPool; -import jodd.util.StringUtil; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static jodd.util.StringPool.CRLF; - -/** - * Base class for {@link HttpRequest} and {@link HttpResponse}. - */ -public abstract class HttpBase { - - public static class Defaults { - - public static final int DEFAULT_PORT = -1; - - /** - * Default HTTP query parameters encoding (UTF-8). - */ - public static String queryEncoding = "UTF-8"; - /** - * Default form encoding (UTF-8). - */ - public static String formEncoding = "UTF-8"; - /** - * Default body media type. - */ - public static String bodyMediaType = MimeTypes.MIME_TEXT_HTML; - /** - * Default body encoding (UTF-8). - */ - public static String bodyEncoding = "UTF-8"; - /** - * Default user agent value. - */ - public static String userAgent = "Jodd HTTP"; - /** - * Flag that controls if headers should be rewritten and capitalized in PascalCase. - * When disabled, header keys are used as they are passed. - * When flag is enabled, header keys will be capitalized. - */ - public static boolean capitalizeHeaderKeys = true; - - } - - public static final String HEADER_ACCEPT = "Accept"; - public static final String HEADER_AUTHORIZATION = "Authorization"; - public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - public static final String HEADER_CONTENT_TYPE = "Content-Type"; - public static final String HEADER_CONTENT_LENGTH = "Content-Length"; - public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; - public static final String HEADER_HOST = "Host"; - public static final String HEADER_ETAG = "ETag"; - public static final String HEADER_CONNECTION = "Connection"; - public static final String HEADER_KEEP_ALIVE = "Keep-Alive"; - public static final String HEADER_CLOSE = "Close"; - public static final String HTTP_1_0 = "HTTP/1.0"; - public static final String HTTP_1_1 = "HTTP/1.1"; - - protected String httpVersion = HTTP_1_1; - protected boolean capitalizeHeaderKeys = Defaults.capitalizeHeaderKeys; - protected final HeadersMultiMap headers = new HeadersMultiMap(); - - protected HttpMultiMap form; // holds form data (when used) - protected String body; // holds raw body string (always) - - @SuppressWarnings("unchecked") - protected T _this() { - return (T) this; - } - - // ---------------------------------------------------------------- properties - - /** - * Returns HTTP version string. By default it's "HTTP/1.1". - */ - public String httpVersion() { - return httpVersion; - } - - /** - * Sets the HTTP version string. Must be formed like "HTTP/1.1". - */ - public T httpVersion(final String httpVersion) { - this.httpVersion = httpVersion; - return _this(); - } - - /** - * Returns whether header keys should be strict or not, when they are - * modified by changing them to PascalCase. - * @see Defaults#capitalizeHeaderKeys - */ - public boolean capitalizeHeaderKeys() { - return capitalizeHeaderKeys; - } - - /** - * Sets headers behavior. - * @see Defaults#capitalizeHeaderKeys - */ - public T capitalizeHeaderKeys(final boolean capitalizeHeaderKeys) { - this.capitalizeHeaderKeys = capitalizeHeaderKeys; - return _this(); - } - - // ---------------------------------------------------------------- headers - - /** - * Returns value of header parameter. - * If multiple headers with the same names exist, - * the first value will be returned. Returns null - * if header doesn't exist. - */ - public String header(final String name) { - return headers.getHeader(name); - } - - /** - * Returns all values for given header name. - */ - public List headers(final String name) { - return headers.getAll(name); - } - - /** - * Removes all header parameters for given name. - */ - public void headerRemove(final String name) { - headers.remove(name.trim()); - } - - /** - * Adds header parameter. If a header with the same name exist, - * it will not be overwritten, but the new header with the same - * name is going to be added. - * The order of header parameters is preserved. - * Also detects 'Content-Type' header and extracts - * {@link #mediaType() media type} and {@link #charset() charset} - * values. - */ - public T header(final String name, final String value) { - return _header(name, value, false); - } - - /** - * Adds many header parameters at once. - * @see #header(String, String) - */ - public T header(final Map headerMap) { - for (final Map.Entry entry : headerMap.entrySet()) { - header(entry.getKey(), entry.getValue()); - } - return _this(); - } - - /** - * Sets the header by overwriting it. - * @see #header(String, String) - */ - public T headerOverwrite(final String name, final String value) { - return _header(name, value, true); - } - - /** - * Adds or sets header parameter. - * @see #header(String, String) - */ - protected T _header(final String name, String value, final boolean overwrite) { - final String key = name.trim(); - - if (key.equalsIgnoreCase(HEADER_CONTENT_TYPE)) { - value = value.trim(); - - mediaType = HttpUtil.extractMediaType(value); - charset = HttpUtil.extractContentTypeCharset(value); - } - - _headerRaw(name, value, overwrite); - - return _this(); - } - - /** - * Internal direct header setting. - */ - protected void _headerRaw(String name, String value, final boolean overwrite) { - name = name.trim(); - value = value.trim(); - - if (overwrite) { - headers.setHeader(name, value); - } else { - headers.addHeader(name, value); - } - } - - /** - * Adds int value as header parameter, - * @see #header(String, String) - */ - public T header(final String name, final int value) { - _headerRaw(name, String.valueOf(value), false); - return _this(); - } - - /** - * Adds date value as header parameter. - * @see #header(String, String) - */ - public T header(final String name, final long millis) { - _headerRaw(name, TimeUtil.formatHttpDate(millis), false); - return _this(); - } - - /** - * Returns collection of all header names. Depends on - * {@link #capitalizeHeaderKeys()} flag. - */ - public Collection headerNames() { - return headers.names(); - } - - /** - * Returns Bearer token or {@code null} if not set. - */ - public String tokenAuthentication() { - final String value = headers.get(HEADER_AUTHORIZATION); - if (value == null) { - return null; - } - final int ndx = value.indexOf("Bearer "); - if (ndx == -1) { - return null; - } - return value.substring(ndx + 7).trim(); - } - - - // ---------------------------------------------------------------- content type - - protected String charset; - - /** - * Returns charset, as defined by 'Content-Type' header. - * If not set, returns null - indicating - * the default charset (ISO-8859-1). - */ - public String charset() { - return charset; - } - - /** - * Defines just content type charset. Setting this value to - * null will remove the charset information from - * the header. - */ - public T charset(final String charset) { - this.charset = null; - contentType(null, charset); - return _this(); - } - - - protected String mediaType; - - /** - * Returns media type, as defined by 'Content-Type' header. - * If not set, returns null - indicating - * the default media type, depending on request/response. - */ - public String mediaType() { - return mediaType; - } - - /** - * Defines just content media type. - * Setting this value to null will - * not have any effects. - */ - public T mediaType(final String mediaType) { - contentType(mediaType, null); - return _this(); - } - - /** - * Returns full "Content-Type" header. - * It consists of {@link #mediaType() media type} - * and {@link #charset() charset}. - */ - public String contentType() { - return header(HEADER_CONTENT_TYPE); - } - - /** - * Sets full "Content-Type" header. Both {@link #mediaType() media type} - * and {@link #charset() charset} are overridden. - */ - public T contentType(final String contentType) { - headerOverwrite(HEADER_CONTENT_TYPE, contentType); - return _this(); - } - - /** - * Sets "Content-Type" header by defining media-type and/or charset parameter. - * This method may be used to update media-type and/or charset by passing - * non-null value for changes. - *

- * Important: if Content-Type header has some other parameters, they will be removed! - */ - public T contentType(String mediaType, String charset) { - if (mediaType == null) { - mediaType = this.mediaType; - } else { - this.mediaType = mediaType; - } - - if (charset == null) { - charset = this.charset; - } else { - this.charset = charset; - } - - String contentType = mediaType; - if (charset != null) { - contentType += ";charset=" + charset; - } - - _headerRaw(HEADER_CONTENT_TYPE, contentType, true); - return _this(); - } - - // ---------------------------------------------------------------- keep-alive - - /** - * Defines "Connection" header as "Keep-Alive" or "Close". - * Existing value is overwritten. - */ - public T connectionKeepAlive(final boolean keepAlive) { - if (keepAlive) { - headerOverwrite(HEADER_CONNECTION, HEADER_KEEP_ALIVE); - } else { - headerOverwrite(HEADER_CONNECTION, HEADER_CLOSE); - } - return _this(); - } - - /** - * Returns true if connection is persistent. - * If "Connection" header does not exist, returns true - * for HTTP 1.1 and false for HTTP 1.0. If - * "Connection" header exist, checks if it is equal to "Close". - *

- * In HTTP 1.1, all connections are considered persistent unless declared otherwise. - * Under HTTP 1.0, there is no official specification for how keepalive operates. - */ - public boolean isConnectionPersistent() { - final String connection = header(HEADER_CONNECTION); - if (connection == null) { - return !httpVersion.equalsIgnoreCase(HTTP_1_0); - } - - return !connection.equalsIgnoreCase(HEADER_CLOSE); - } - - // ---------------------------------------------------------------- common headers - - /** - * Returns full "Content-Length" header or - * null if not set. Returned value is raw and unchecked, exactly the same - * as it was specified or received. It may be even invalid. - */ - public String contentLength() { - return header(HEADER_CONTENT_LENGTH); - } - - /** - * Sets the full "Content-Length" header. - */ - public T contentLength(final int value) { - _headerRaw(HEADER_CONTENT_LENGTH, String.valueOf(value), true); - return _this(); - } - - /** - * Returns "Content-Encoding" header. - */ - public String contentEncoding() { - return header(HEADER_CONTENT_ENCODING); - } - - /** - * Returns "Accept" header. - */ - public String accept() { - return header(HEADER_ACCEPT); - } - - /** - * Sets "Accept" header. - */ - public T accept(final String encodings) { - headerOverwrite(HEADER_ACCEPT, encodings); - return _this(); - } - - /** - * Returns "Accept-Encoding" header. - */ - public String acceptEncoding() { - return header(HEADER_ACCEPT_ENCODING); - } - - /** - * Sets "Accept-Encoding" header. - */ - public T acceptEncoding(final String encodings) { - headerOverwrite(HEADER_ACCEPT_ENCODING, encodings); - return _this(); - } - - // ---------------------------------------------------------------- form - - /** - * Initializes form. - */ - protected void initForm() { - if (form == null) { - form = HttpMultiMap.newCaseInsensitiveMap(); - } - } - - /** - * Wraps non-Strings form values with {@link jodd.http.up.Uploadable uploadable content}. - * Detects invalid types and throws an exception. So all uploadable values - * are of the same type. - */ - protected Object wrapFormValue(final Object value) { - if (value == null) { - return null; - } - if (value instanceof CharSequence) { - return value.toString(); - } - if (value instanceof Number) { - return value.toString(); - } - if (value instanceof Boolean) { - return value.toString(); - } - if (value instanceof File) { - return new FileUploadable((File) value); - } - if (value instanceof byte[]) { - return new ByteArrayUploadable((byte[]) value, null); - } - if (value instanceof Uploadable) { - return value; - } - - throw new HttpException("Unsupported value type: " + value.getClass().getName()); - } - - /** - * Adds the form parameter. Existing parameter will not be overwritten. - */ - public T form(final String name, Object value) { - initForm(); - - value = wrapFormValue(value); - ((HttpMultiMap)form).add(name, value); - - return _this(); - } - - /** - * Sets form parameter by overwriting. - */ - public T formOverwrite(final String name, Object value) { - initForm(); - - value = wrapFormValue(value); - ((HttpMultiMap)form).set(name, value); - - return _this(); - } - - /** - * Sets many form parameters at once. - */ - public T form(String name, final Object value, final Object... parameters) { - initForm(); - - form(name, value); - - for (int i = 0; i < parameters.length; i += 2) { - name = parameters[i].toString(); - - form(name, parameters[i + 1]); - } - return _this(); - } - - /** - * Sets many form parameters at once. - */ - public T form(final Map formMap) { - initForm(); - - for (final Map.Entry entry : formMap.entrySet()) { - form(entry.getKey(), entry.getValue()); - } - return _this(); - } - - /** - * Return map of form parameters. - * Note that all uploadable values are wrapped with {@link jodd.http.up.Uploadable}. - */ - public HttpMultiMap form() { - return form; - } - - // ---------------------------------------------------------------- form encoding - - protected String formEncoding = Defaults.formEncoding; - - /** - * Defines encoding for forms parameters. Default value is - * copied from {@link Defaults#formEncoding}. - * It is overridden by {@link #charset() charset} value. - */ - public T formEncoding(final String encoding) { - this.formEncoding = encoding; - return _this(); - } - - // ---------------------------------------------------------------- cookies - - /** - * Parses cookie information from the header. - */ - public Cookie[] cookies() { - final String cookieHeader = header("cookie"); - if (!StringUtil.isNotBlank(cookieHeader)) { - return new Cookie[0]; - } - - return Arrays - .stream(StringUtil.splitc(cookieHeader, ';')) - .map(Cookie::new) - .toArray(Cookie[]::new); - } - - // ---------------------------------------------------------------- body - - /** - * Returns raw body as received or set (always in ISO-8859-1 encoding). - * If body content is a text, use {@link #bodyText()} to get it converted. - * Returns null if body is not specified! - */ - public String body() { - return body; - } - - /** - * Returns raw body bytes. Returns null if body is not specified. - */ - public byte[] bodyBytes() { - if (body == null) { - return null; - } - return body.getBytes(StandardCharsets.ISO_8859_1); - } - - /** - * Returns {@link #body() body content} as text. If {@link #charset() charset parameter} - * of "Content-Type" header is defined, body string charset is converted, otherwise - * the same raw body content is returned. Never returns null. - */ - public String bodyText() { - if (body == null) { - return StringPool.EMPTY; - } - if (charset != null) { - return StringUtil.convertCharset(body, StandardCharsets.ISO_8859_1, Charset.forName(charset)); - } - return body(); - } - - /** - * Sets raw body content and discards all form parameters. - * Important: body string is in RAW format, meaning, ISO-8859-1 encoding. - * Also sets "Content-Length" parameter. However, "Content-Type" is not set - * and it is expected from user to set this one. - */ - public T body(final String body) { - this.body = body; - this.form = null; - contentLength(body.length()); - return _this(); - } - - /** - * Defines body text and content type (as media type and charset). - * Body string will be converted to {@link #body(String) raw body string} - * and "Content-Type" header will be set. - */ - public T bodyText(String body, final String mediaType, final String charset) { - body = StringUtil.convertCharset(body, Charset.forName(charset), StandardCharsets.ISO_8859_1); - contentType(mediaType, charset); - body(body); - return _this(); - } - - /** - * Defines {@link #bodyText(String, String, String) body text content} - * that will be encoded in {@link Defaults#bodyEncoding default body encoding}. - */ - public T bodyText(final String body, final String mediaType) { - return bodyText(body, mediaType, charset != null ? charset : Defaults.bodyEncoding); - } - /** - * Defines {@link #bodyText(String, String, String) body text content} - * that will be encoded as {@link Defaults#bodyMediaType default body media type} - * in {@link Defaults#bodyEncoding default body encoding} if missing. - */ - public T bodyText(final String body) { - return bodyText( - body, - mediaType != null ? mediaType : Defaults.bodyMediaType, - charset != null ? charset : Defaults.bodyEncoding); - } - - /** - * Sets raw body content and discards form parameters. - * Also sets "Content-Length" and "Content-Type" parameter. - * @see #body(String) - */ - public T body(final byte[] content, final String contentType) { - String body = null; - try { - body = new String(content, StandardCharsets.ISO_8859_1.name()); - } catch (final UnsupportedEncodingException ignore) { - } - contentType(contentType); - return body(body); - } - - // ---------------------------------------------------------------- body form - - protected boolean multipart = false; - - /** - * Returns true if form contains {@link jodd.http.up.Uploadable}. - */ - protected boolean isFormMultipart() { - if (multipart) { - return true; - } - - for (final Map.Entry entry : form) { - final Object value = entry.getValue(); - if (value instanceof Uploadable) { - return true; - } - } - - return false; - } - - /** - * Creates form {@link jodd.http.Buffer buffer} and sets few headers. - */ - protected Buffer formBuffer() { - final Buffer buffer = new Buffer(); - if (form == null || form.isEmpty()) { - return buffer; - } - - if (!isFormMultipart()) { - final String formEncoding = resolveFormEncoding(); - - // encode - final String formQueryString = HttpUtil.buildQuery(form, formEncoding); - - contentType("application/x-www-form-urlencoded", null); - contentLength(formQueryString.length()); - - buffer.append(formQueryString); - return buffer; - } - - final String boundary = StringUtil.repeat('-', 10) + RandomString.get().randomAlphaNumeric(10); - - for (final Map.Entry entry : form) { - - buffer.append("--"); - buffer.append(boundary); - buffer.append(CRLF); - - final String name = entry.getKey(); - final Object value = entry.getValue(); - - if (value instanceof String) { - final String string = (String) value; - buffer.append("Content-Disposition: form-data; name=\"").append(name).append('"').append(CRLF); - buffer.append(CRLF); - - final String formEncoding = resolveFormEncoding(); - - final String utf8String = StringUtil.convertCharset( - string, Charset.forName(formEncoding), StandardCharsets.ISO_8859_1); - - buffer.append(utf8String); - } - else if (value instanceof Uploadable) { - final Uploadable uploadable = (Uploadable) value; - - String fileName = uploadable.getFileName(); - if (fileName == null) { - fileName = name; - } else { - final String formEncoding = resolveFormEncoding(); - - fileName = StringUtil.convertCharset( - fileName, Charset.forName(formEncoding), StandardCharsets.ISO_8859_1); - } - - buffer.append("Content-Disposition: form-data; name=\"").append(name); - buffer.append("\"; filename=\"").append(fileName).append('"').append(CRLF); - - String mimeType = uploadable.getMimeType(); - if (mimeType == null) { - mimeType = MimeTypes.getMimeType(FileNameUtil.getExtension(fileName)); - } - buffer.append(HEADER_CONTENT_TYPE).append(": ").append(mimeType).append(CRLF); - - buffer.append("Content-Transfer-Encoding: binary").append(CRLF); - buffer.append(CRLF); - - buffer.append(uploadable); - - //byte[] bytes = uploadable.getBytes(); - //for (byte b : bytes) { - //buffer.append(CharUtil.toChar(b)); - //} - } else { - // should never happened! - throw new HttpException("Unsupported type"); - } - buffer.append(CRLF); - } - - buffer.append("--").append(boundary).append("--"); - buffer.append(CRLF); - - // the end - contentType("multipart/form-data; boundary=" + boundary); - contentLength(buffer.size()); - - return buffer; - } - - /** - * Resolves form encodings. - */ - protected String resolveFormEncoding() { - // determine form encoding - String formEncoding = charset; - - if (formEncoding == null) { - formEncoding = this.formEncoding; - } - return formEncoding; - } - - // ---------------------------------------------------------------- buffer - - /** - * Returns string representation of this request or response. - */ - @Override - public String toString() { - return toString(true); - } - - /** - * Returns full request/response, or just headers. - * Useful for debugging. - */ - public String toString(final boolean fullResponse) { - final Buffer buffer = buffer(fullResponse); - - final StringWriter stringWriter = new StringWriter(); - - try { - buffer.writeTo(stringWriter); - } - catch (final IOException ioex) { - throw new HttpException(ioex); - } - - return stringWriter.toString(); - } - - /** - * Returns byte array of request or response. - */ - public byte[] toByteArray() { - final Buffer buffer = buffer(true); - - final ByteArrayOutputStream baos = new ByteArrayOutputStream(buffer.size()); - - try { - buffer.writeTo(baos); - } - catch (final IOException ioex) { - throw new HttpException(ioex); - } - - return baos.toByteArray(); - } - - /** - * Creates {@link jodd.http.Buffer buffer} ready to be consumed. - * Buffer can, optionally, contains just headers. - */ - protected abstract Buffer buffer(boolean full); - - protected void populateHeaderAndBody(final Buffer target, final Buffer formBuffer, final boolean fullRequest) { - for (final String name : headers.names()) { - final List values = headers.getAll(name); - - final String key = capitalizeHeaderKeys ? HttpUtil.prepareHeaderParameterName(name) : name; - - target.append(key); - target.append(": "); - int count = 0; - - for (final String value : values) { - if (count++ > 0) { - target.append(", "); - } - target.append(value); - } - - target.append(CRLF); - } - - if (fullRequest) { - target.append(CRLF); - - if (form != null) { - target.append(formBuffer); - } else if (body != null) { - target.append(body); - } - } - } - - - // ---------------------------------------------------------------- send - - protected HttpProgressListener httpProgressListener; - - /** - * Sends request or response to output stream. - */ - public void sendTo(final OutputStream out) throws IOException { - final Buffer buffer = buffer(true); - - if (httpProgressListener == null) { - buffer.writeTo(out); - } - else { - buffer.writeTo(out, httpProgressListener); - } - - out.flush(); - } - - // ---------------------------------------------------------------- parsing - - /** - * Parses headers. - */ - protected void readHeaders(final BufferedReader reader) { - while (true) { - final String line; - try { - line = reader.readLine(); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - if (StringUtil.isBlank(line)) { - break; - } - - final int ndx = line.indexOf(':'); - if (ndx != -1) { - header(line.substring(0, ndx), line.substring(ndx + 1)); - } else { - throw new HttpException("Invalid header: " + line); - } - } - } - - /** - * Parses body. - */ - protected void readBody(final BufferedReader reader) { - String bodyString = null; - - // first determine if chunked encoding is specified - boolean isChunked = false; - - final String transferEncoding = header("Transfer-Encoding"); - if (transferEncoding != null && transferEncoding.equalsIgnoreCase("chunked")) { - isChunked = true; - } - - - // content length - final String contentLen = contentLength(); - int contentLenValue = -1; - - if (contentLen != null && !isChunked) { - contentLenValue = Integer.parseInt(contentLen); - - if (contentLenValue > 0) { - final FastCharArrayWriter fastCharArrayWriter = new FastCharArrayWriter(contentLenValue); - - try { - IOUtil.copy(reader, fastCharArrayWriter, contentLenValue); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - bodyString = fastCharArrayWriter.toString(); - } - } - - // chunked encoding - if (isChunked) { - - final FastCharArrayWriter fastCharArrayWriter = new FastCharArrayWriter(); - - try { - while (true) { - final String line = reader.readLine(); - - final int len; - try { - len = Integer.parseInt(line, 16); - } catch (final NumberFormatException nfex) { - throw new HttpException("Invalid chunk length: " + line); - } - - if (len > 0) { - IOUtil.copy(reader, fastCharArrayWriter, len); - reader.readLine(); - } else { - // end reached, read trailing headers, if there is any - readHeaders(reader); - break; - } - } - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - bodyString = fastCharArrayWriter.toString(); - } - - // no body yet - special case - if (bodyString == null && contentLenValue != 0) { - // body ends when stream closes - final FastCharArrayWriter fastCharArrayWriter = new FastCharArrayWriter(); - try { - IOUtil.copy(reader, fastCharArrayWriter); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - bodyString = fastCharArrayWriter.toString(); - } - - // BODY READY - PARSE BODY - String charset = this.charset; - if (charset == null) { - charset = StandardCharsets.ISO_8859_1.name(); - } - body = bodyString; - - String mediaType = mediaType(); - - if (mediaType == null) { - mediaType = StringPool.EMPTY; - } else { - mediaType = mediaType.toLowerCase(); - } - - if (mediaType.equals("application/x-www-form-urlencoded")) { - form = HttpUtil.parseQuery(bodyString, true); - return; - } - - if (mediaType.equals("multipart/form-data")) { - form = HttpMultiMap.newCaseInsensitiveMap(); - - final MultipartStreamParser multipartParser = new MultipartStreamParser(); - - try { - final byte[] bodyBytes = bodyString.getBytes(StandardCharsets.ISO_8859_1.name()); - final ByteArrayInputStream bin = new ByteArrayInputStream(bodyBytes); - multipartParser.parseRequestStream(bin, charset); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - // string parameters - for (final String paramName : multipartParser.getParameterNames()) { - final String[] values = multipartParser.getParameterValues(paramName); - - for (final String value : values) { - ((HttpMultiMap)form).add(paramName, value); - } - } - - // file parameters - for (final String paramName : multipartParser.getFileParameterNames()) { - final FileUpload[] uploads = multipartParser.getFiles(paramName); - - for (final FileUpload upload : uploads) { - ((HttpMultiMap)form).add(paramName, upload); - } - } - - return; - } - - // body is a simple content - - form = null; - } - -} diff --git a/jodd-http/src/main/java/jodd/http/HttpBrowser.java b/jodd-http/src/main/java/jodd/http/HttpBrowser.java deleted file mode 100644 index 36162cb3e..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpBrowser.java +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.exception.ExceptionUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Emulates HTTP Browser and persist cookies between requests. - */ -public class HttpBrowser { - - protected HttpConnectionProvider httpConnectionProvider; - protected HttpRequest httpRequest; - protected HttpResponse httpResponse; - protected HttpMultiMap cookies = HttpMultiMap.newCaseInsensitiveMap(); - protected HeadersMultiMap defaultHeaders = new HeadersMultiMap(); - protected boolean keepAlive; - protected long elapsedTime; - protected boolean catchTransportExceptions = true; - - public HttpBrowser() { - httpConnectionProvider = HttpConnectionProvider.get(); - } - - /** - * Returns true if keep alive is used. - */ - public boolean isKeepAlive() { - return keepAlive; - } - - /** - * Defines that persistent HTTP connection should be used. - */ - public HttpBrowser setKeepAlive(final boolean keepAlive) { - this.keepAlive = keepAlive; - return this; - } - - /** - * Defines if transport exceptions should be thrown. - */ - public HttpBrowser setCatchTransportExceptions(final boolean catchTransportExceptions) { - this.catchTransportExceptions = catchTransportExceptions; - return this; - } - - /** - * Defines proxy for a browser. - */ - public HttpBrowser setProxyInfo(final ProxyInfo proxyInfo) { - httpConnectionProvider.useProxy(proxyInfo); - return this; - } - - /** - * Defines {@link jodd.http.HttpConnectionProvider} for this browser session. - * Resets the previous proxy definition, if set. - */ - public HttpBrowser setHttpConnectionProvider(final HttpConnectionProvider httpConnectionProvider) { - this.httpConnectionProvider = httpConnectionProvider; - return this; - } - - /** - * Adds default header to all requests. - */ - public HttpBrowser setDefaultHeader(final String name, final String value) { - defaultHeaders.addHeader(name, value); - return this; - } - - /** - * Returns last used request. - */ - public HttpRequest getHttpRequest() { - return httpRequest; - } - - /** - * Returns last received {@link HttpResponse HTTP response} object. - */ - public HttpResponse getHttpResponse() { - return httpResponse; - } - - /** - * Returns last response HTML page. - */ - public String getPage() { - if (httpResponse == null) { - return null; - } - return httpResponse.bodyText(); - } - - - /** - * Sends new request as a browser. Before sending, - * all browser cookies are added to the request. - * After sending, the cookies are read from the response. - * Moreover, status codes 301 and 302 are automatically - * handled. Returns very last response. - */ - public HttpResponse sendRequest(HttpRequest httpRequest) { - elapsedTime = System.currentTimeMillis(); - - // send request - - httpRequest.followRedirects(false); - - // default setting - - final boolean verifyHttpsHost = httpRequest.verifyHttpsHost(); - final boolean trustAllCerts = httpRequest.trustAllCertificates(); - - while (true) { - this.httpRequest = httpRequest; - final HttpResponse previousResponse = this.httpResponse; - this.httpResponse = null; - - addDefaultHeaders(httpRequest); - addCookies(httpRequest); - - httpRequest.verifyHttpsHost(verifyHttpsHost); - httpRequest.trustAllCerts(trustAllCerts); - - // send request - if (catchTransportExceptions) { - try { - this.httpResponse = _sendRequest(httpRequest, previousResponse); - } - catch (final HttpException httpException) { - httpResponse = new HttpResponse(); - httpResponse.assignHttpRequest(httpRequest); - httpResponse.statusCode(503); - httpResponse.statusPhrase("Service unavailable. " + ExceptionUtil.message(httpException)); - } - } - else { - this.httpResponse =_sendRequest(httpRequest, previousResponse); - } - - readCookies(httpResponse); - - final int statusCode = httpResponse.statusCode(); - - // 301: moved permanently - if (statusCode == 301) { - final String newPath = httpResponse.location(); - - if (newPath == null) { - break; - } - - httpRequest = HttpRequest.get(newPath); - continue; - } - - // 302: redirect, 303: see other - if (statusCode == 302 || statusCode == 303) { - final String newPath = httpResponse.location(); - - if (newPath == null) { - break; - } - - httpRequest = HttpRequest.get(newPath); - continue; - } - - // 307: temporary redirect, 308: permanent redirect - if (statusCode == 307 || statusCode == 308) { - final String newPath = httpResponse.location(); - - if (newPath == null) { - break; - } - - final String originalMethod = httpRequest.method(); - httpRequest = new HttpRequest() - .method(originalMethod) - .set(newPath); - continue; - } - - break; - } - - elapsedTime = System.currentTimeMillis() - elapsedTime; - - return this.httpResponse; - } - - /** - * Opens connection and sends a response. - */ - protected HttpResponse _sendRequest(final HttpRequest httpRequest, final HttpResponse previouseResponse) { - if (!keepAlive) { - httpRequest.open(httpConnectionProvider); - } else { - // keeping alive - if (previouseResponse == null) { - httpRequest.open(httpConnectionProvider).connectionKeepAlive(true); - } else { - httpRequest.keepAlive(previouseResponse, true); - } - } - - return httpRequest.send(); - } - - /** - * Add default headers to the request. If request already has a header set, - * default header will be ignored. - */ - protected void addDefaultHeaders(final HttpRequest httpRequest) { - for (final Map.Entry entry : defaultHeaders.entries()) { - final String name = entry.getKey(); - - if (!httpRequest.headers.contains(name)) { - httpRequest.headers.add(name, entry.getValue()); - } - } - } - - /** - * Returns elapsed time of last {@link #sendRequest(HttpRequest)} in milliseconds. - */ - public long getElapsedTime() { - return elapsedTime; - } - - // ---------------------------------------------------------------- close - - /** - * Closes browser explicitly, needed when keep-alive connection is used. - */ - public void close() { - if (httpResponse != null) { - httpResponse.close(); - } - } - - // ---------------------------------------------------------------- cookies - - /** - * Deletes all cookies. - */ - public void clearCookies() { - cookies.clear(); - } - - /** - * Reads cookies from response and adds to cookies list. - */ - protected void readCookies(final HttpResponse httpResponse) { - final Cookie[] newCookies = httpResponse.cookies(); - - for (final Cookie cookie : newCookies) { - cookies.set(cookie.getName(), cookie); - } - } - - /** - * Add cookies to the request. - */ - protected void addCookies(final HttpRequest httpRequest) { - // prepare all cookies - final List cookiesList = new ArrayList<>(); - - for (final Cookie cookie : httpRequest.cookies()) { - cookies.set(cookie.getName(),cookie); - } - - if (!cookies.isEmpty()) { - for (final Map.Entry cookieEntry : cookies) { - cookiesList.add(cookieEntry.getValue()); - } - - httpRequest.cookies(cookiesList.toArray(new Cookie[0])); - } - } -} diff --git a/jodd-http/src/main/java/jodd/http/HttpConnection.java b/jodd-http/src/main/java/jodd/http/HttpConnection.java deleted file mode 100644 index 78a7b0f65..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpConnection.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Http connection. Created by {@link HttpConnectionProvider}. - */ -public interface HttpConnection { - - /** - * Initializes http connection after socket is created. - * Applies configurations, like {@link #setTimeout(int)}. - */ - public void init() throws IOException; - - /** - * Returns connection output stream. - */ - public OutputStream getOutputStream() throws IOException; - - /** - * Returns connection input stream. - */ - public InputStream getInputStream() throws IOException; - - /** - * Closes connection. Ignores all exceptions. - */ - public void close(); - - /** - * Sets the timeout for connections, in milliseconds. With this option set to a non-zero timeout, - * connection will block for only this amount of time. If the timeout expires, an Exception is raised. - * The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. - */ - void setTimeout(int milliseconds); - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/HttpConnectionProvider.java b/jodd-http/src/main/java/jodd/http/HttpConnectionProvider.java deleted file mode 100644 index 210f0b0ee..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpConnectionProvider.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.net.SocketHttpConnectionProvider; - -import java.io.IOException; - -/** - * Factory for {@link HttpConnection http connections}. - */ -public interface HttpConnectionProvider { - - class Implementation { - private static HttpConnectionProvider httpConnectionProvider = new SocketHttpConnectionProvider(); - - public static void set(final HttpConnectionProvider httpConnectionProvider) { - Implementation.httpConnectionProvider = httpConnectionProvider; - } - } - - /** - * Returns default implementation of connection provider. - */ - static HttpConnectionProvider get() { - return Implementation.httpConnectionProvider; - } - - - /** - * Specifies {@link ProxyInfo proxy} for provide to use. - */ - public void useProxy(ProxyInfo proxyInfo); - - /** - * Creates new {@link HttpConnection} - * from {@link jodd.http.HttpRequest request}. - */ - public HttpConnection createHttpConnection(HttpRequest httpRequest) throws IOException; - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/HttpException.java b/jodd-http/src/main/java/jodd/http/HttpException.java deleted file mode 100644 index 553599d89..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpException.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.exception.UncheckedException; - -/** - * HTTP exception. - */ -public class HttpException extends UncheckedException { - - public HttpException(final Throwable t) { - super(t); - } - - public HttpException(final String message) { - super(message); - } - - public HttpException(final Object networkObject, final String message) { - super(networkObject.toString() + ": " + message); - } - - public HttpException(final String message, final Throwable t) { - super(message, t); - } - - public HttpException(final Object networkObject, final String message, final Throwable t) { - super(networkObject.toString() + ": " + message, t); - } -} diff --git a/jodd-http/src/main/java/jodd/http/HttpMultiMap.java b/jodd-http/src/main/java/jodd/http/HttpMultiMap.java deleted file mode 100644 index 00480d1b9..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpMultiMap.java +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeSet; - -/** - * General purpose HTTP multi-map. It's optimized Linked-HashMap, designed for - * small number of items and String non-null keys. It stores keys - * in case-sensitive way, but, by default, you can read them in case-insensitive - * way. - */ -public class HttpMultiMap implements Iterable> { - - private static final int BUCKET_SIZE = 31; - private final boolean caseSensitive; - - @SuppressWarnings("unchecked") - private final MapEntry[] entries = new MapEntry[BUCKET_SIZE + 1]; - private final MapEntry head = new MapEntry<>(-1, null, null); - - /** - * Creates new case-insensitive multimap. - */ - public static HttpMultiMap newCaseInsensitiveMap() { - return new HttpMultiMap<>(false); - } - /** - * Creates new case-insensitive map. - */ - public static HttpMultiMap newCaseSensitiveMap() { - return new HttpMultiMap<>(true); - } - - protected HttpMultiMap(final boolean caseSensitive) { - head.before = head.after = head; - this.caseSensitive = caseSensitive; - } - - /** - * Calculates hash value of the input string. - */ - private int hash(final String name) { - int h = 0; - for (int i = name.length() - 1; i >= 0; i--) { - char c = name.charAt(i); - if (!caseSensitive) { - if (c >= 'A' && c <= 'Z') { - c += 32; - } - } - h = 31 * h + c; - } - - if (h > 0) { - return h; - } - if (h == Integer.MIN_VALUE) { - return Integer.MAX_VALUE; - } - return -h; - } - - /** - * Calculates bucket index from the hash. - */ - private static int index(final int hash) { - return hash & BUCKET_SIZE; - } - - /** - * Returns true if two names are the same. - */ - private boolean eq(final String name1, final String name2) { - int nameLen = name1.length(); - if (nameLen != name2.length()) { - return false; - } - - for (int i = nameLen - 1; i >= 0; i--) { - char c1 = name1.charAt(i); - char c2 = name2.charAt(i); - - if (c1 != c2) { - if (caseSensitive) { - return false; - } - if (c1 >= 'A' && c1 <= 'Z') { - c1 += 32; - } - if (c2 >= 'A' && c2 <= 'Z') { - c2 += 32; - } - if (c1 != c2) { - return false; - } - } - } - return true; - } - - // ---------------------------------------------------------------- basic - - /** - * Returns the number of keys. This is not the number of all elements. - * Not optimized. - */ - public int size() { - return names().size(); - } - - /** - * Clears the map. - */ - public HttpMultiMap clear() { - for (int i = 0; i < entries.length; i++) { - entries[i] = null; - } - head.before = head.after = head; - return this; - } - - /** - * Returns true if name exist. - */ - public boolean contains(final String name) { - return getEntry(name) != null; - } - - /** - * Returns true if map is empty. - */ - public boolean isEmpty() { - return head == head.after; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : this) { - sb.append(entry).append('\n'); - } - return sb.toString(); - } - - // ---------------------------------------------------------------- set/add - - private HttpMultiMap _set(final Iterable> map) { - clear(); - for (Map.Entry entry : map) { - add(entry.getKey(), entry.getValue()); - } - return this; - } - - public HttpMultiMap setAll(final HttpMultiMap multiMap) { - return _set(multiMap); - } - - public HttpMultiMap setAll(final Map map) { - return _set(map.entrySet()); - } - - public HttpMultiMap set(final String name, final V value) { - int h = hash(name); - int i = index(h); - _remove(h, i, name); - _add(h, i, name, value); - return this; - } - - public HttpMultiMap setAll(final String name, final Iterable values) { - int h = hash(name); - int i = index(h); - - _remove(h, i, name); - for (V v : values) { - _add(h, i, name, v); - } - - return this; - } - - public HttpMultiMap add(final String name, final V value) { - int h = hash(name); - int i = index(h); - _add(h, i, name, value); - return this; - } - - public HttpMultiMap addAll(final String name, final Iterable values) { - int h = hash(name); - int i = index(h); - for (V value : values) { - _add(h, i, name, value); - } - return this; - } - - public HttpMultiMap addAll(final HttpMultiMap map) { - for (Map.Entry entry : map.entries()) { - add(entry.getKey(), entry.getValue()); - } - return this; - } - - public HttpMultiMap addAll(final Map map) { - for (Map.Entry entry : map.entrySet()) { - add(entry.getKey(), entry.getValue()); - } - return this; - } - - private void _add(final int hash, final int index, final String name, final V value) { - // update the hash table - MapEntry e = entries[index]; - MapEntry newEntry; - entries[index] = newEntry = new MapEntry<>(hash, name, value); - newEntry.next = e; - - // update the linked list - newEntry.addBefore(head); - } - - // ---------------------------------------------------------------- remove - - public HttpMultiMap remove(final String name) { - int h = hash(name); - int i = index(h); - _remove(h, i, name); - return this; - } - - private void _remove(final int hash, final int index, final String name) { - MapEntry e = entries[index]; - if (e == null) { - return; - } - - for (; ; ) { - if (e.hash == hash && eq(name, e.key)) { - e.remove(); - MapEntry next = e.next; - if (next != null) { - entries[index] = next; - e = next; - } - else { - entries[index] = null; - return; - } - } - else { - break; - } - } - - for (; ; ) { - MapEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == hash && eq(name, next.key)) { - e.next = next.next; - next.remove(); - } - else { - e = next; - } - } - } - - // ---------------------------------------------------------------- get - - /** - * Returns the first value from the map associated with the name. - * Returns null if name does not exist or - * if associated value is null. - */ - public V get(final String name) { - Map.Entry entry = getEntry(name); - - if (entry == null) { - return null; - } - return entry.getValue(); - } - - /** - * Returns first entry for given name. Returns null if entry - * does not exist. - */ - public Map.Entry getEntry(final String name) { - int h = hash(name); - int i = index(h); - MapEntry e = entries[i]; - while (e != null) { - if (e.hash == h && eq(name, e.key)) { - return e; - } - - e = e.next; - } - return null; - } - - /** - * Returns all values associated with the name. - */ - public List getAll(final String name) { - LinkedList values = new LinkedList<>(); - - int h = hash(name); - int i = index(h); - MapEntry e = entries[i]; - while (e != null) { - if (e.hash == h && eq(name, e.key)) { - values.addFirst(e.getValue()); - } - e = e.next; - } - return values; - } - - // ---------------------------------------------------------------- iterate - - /** - * Returns iterator of all entries. - */ - @Override - public Iterator> iterator() { - final MapEntry[] e = {head.after}; - - return new Iterator>() { - @Override - public boolean hasNext() { - return e[0] != head; - } - - @Override - @SuppressWarnings("unchecked") - public Map.Entry next() { - if (!hasNext()) { - throw new NoSuchElementException("No next() entry in the iteration"); - } - MapEntry next = e[0]; - e[0] = e[0].after; - return next; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - public Set names() { - Set names = new TreeSet<>(caseSensitive ? null : String.CASE_INSENSITIVE_ORDER); - - MapEntry e = head.after; - while (e != head) { - names.add(e.getKey()); - e = e.after; - } - return names; - } - - /** - * Returns all the entries of this map. Case sensitivity does not influence - * the returned list, it always contains all of the values. - */ - public List> entries() { - List> all = new LinkedList<>(); - - MapEntry e = head.after; - while (e != head) { - all.add(e); - e = e.after; - } - return all; - } - - private static final class MapEntry implements Map.Entry { - final int hash; - final String key; - V value; - MapEntry next; - MapEntry before, after; - - private MapEntry(final int hash, final String key, final V value) { - this.hash = hash; - this.key = key; - this.value = value; - } - - void remove() { - before.after = after; - after.before = before; - } - - void addBefore(final MapEntry e) { - after = e; - before = e.before; - before.after = this; - after.before = this; - } - - @Override - public String getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(final V value) { - V oldValue = this.value; - this.value = value; - return oldValue; - } - - @Override - public String toString() { - return getKey() + ": " + getValue(); - } - } -} diff --git a/jodd-http/src/main/java/jodd/http/HttpProgressListener.java b/jodd-http/src/main/java/jodd/http/HttpProgressListener.java deleted file mode 100644 index bbd0dea2c..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpProgressListener.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -/** - * Http upload progress listener. - */ -public abstract class HttpProgressListener { - - /** - * Total size to transfer. - */ - protected int size; - - /** - * Returns callback size in bytes. By default it returns - * size of 1 percent of a total size. If returned size - * is less then 512, it will be rounded to 512. - * This is also the size of the chunk that is sent over network. - */ - public int callbackSize(final int size) { - this.size = size; - - int callbackSize = (size + 50) / 100; - - if (callbackSize < 512) { - callbackSize = 512; - } - - return callbackSize; - } - - /** - * Callback for every sent {@link #callbackSize(int) chunk}. - * Also called before and after the transfer. - */ - public abstract void transferred(int len); - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/HttpRequest.java b/jodd-http/src/main/java/jodd/http/HttpRequest.java deleted file mode 100644 index 92ddfdd4e..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpRequest.java +++ /dev/null @@ -1,1061 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.net.HttpMethod; -import jodd.net.MimeTypes; -import jodd.util.Base64; -import jodd.util.StringBand; -import jodd.util.StringPool; -import jodd.util.StringUtil; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; - -import static jodd.util.StringPool.CRLF; -import static jodd.util.StringPool.SPACE; - -/** - * HTTP request. - */ -public class HttpRequest extends HttpBase { - - protected String protocol = "http"; - protected String host = "localhost"; - protected int port = Defaults.DEFAULT_PORT; - protected String method = "GET"; - protected String path = StringPool.SLASH; - protected HttpMultiMap query; - - // ---------------------------------------------------------------- init - - public HttpRequest() { - initRequest(); - } - - /** - * Prepares request on creation. By default, it just - * adds "Connection: Close" header. - */ - protected void initRequest() { - connectionKeepAlive(false); - } - - // ---------------------------------------------------------------- properties - - /** - * Returns request host name. - */ - public String host() { - return host; - } - - /** - * Sets request host name. - */ - public HttpRequest host(final String host) { - this.host = host; - if (headers.contains(HEADER_HOST)) { - headerOverwrite(HEADER_HOST, host); - } - return this; - } - - /** - * Returns used protocol. By default it's "http". - */ - public String protocol() { - return protocol; - } - - /** - * Defines protocol. - */ - public HttpRequest protocol(final String protocol) { - this.protocol = protocol; - return this; - } - - /** - * Returns request port number. When port is not - * explicitly defined, returns default port for - * current protocol. - */ - public int port() { - if (port == Defaults.DEFAULT_PORT) { - if (protocol == null) { - return 80; - } - if (protocol.equalsIgnoreCase("https")) { - return 443; - } - return 80; - } - return port; - } - - /** - * Sets request port number. - */ - public HttpRequest port(final int port) { - this.port = port; - return this; - } - - // ---------------------------------------------------------------- set - - /** - * Sets the destination (method, host, port... ) at once. - */ - public HttpRequest set(String destination) { - destination = destination.trim(); - - // http method, optional - - int ndx = destination.indexOf(' '); - - if (ndx != -1) { - final String method = destination.substring(0, ndx).toUpperCase(); - - try { - final HttpMethod httpMethod = HttpMethod.valueOf(method); - this.method = httpMethod.name(); - destination = destination.substring(ndx + 1); - } - catch (final IllegalArgumentException ignore) { - // unknown http method - } - } - - // protocol - - ndx = destination.indexOf("://"); - - if (ndx != -1) { - protocol = destination.substring(0, ndx); - destination = destination.substring(ndx + 3); - } - - // host - - ndx = destination.indexOf('/'); - - if (ndx == -1) { - ndx = destination.length(); - } - - if (ndx != 0) { - - String hostToSet = destination.substring(0, ndx); - destination = destination.substring(ndx); - - // port - - ndx = hostToSet.indexOf(':'); - - if (ndx == -1) { - port = Defaults.DEFAULT_PORT; - } else { - port = Integer.parseInt(hostToSet.substring(ndx + 1)); - hostToSet = hostToSet.substring(0, ndx); - } - - host(hostToSet); - } - - // path + query - - path(destination); - - return this; - } - - // ---------------------------------------------------------------- static factories - - /** - * Generic request builder, usually used when method is a variable. - * Otherwise, use one of the other static request builder methods. - */ - public static HttpRequest create(final String method, final String destination) { - return new HttpRequest() - .method(method.toUpperCase()) - .set(destination); - } - - /** - * Builds a CONNECT request. - */ - public static HttpRequest connect(final String destination) { - return new HttpRequest() - .method(HttpMethod.CONNECT) - .set(destination); - } - /** - * Builds a GET request. - */ - public static HttpRequest get(final String destination) { - return new HttpRequest() - .method(HttpMethod.GET) - .set(destination); - } - /** - * Builds a POST request. - */ - public static HttpRequest post(final String destination) { - return new HttpRequest() - .method(HttpMethod.POST) - .set(destination); - } - /** - * Builds a PUT request. - */ - public static HttpRequest put(final String destination) { - return new HttpRequest() - .method(HttpMethod.PUT) - .set(destination); - } - /** - * Builds a PATCH request. - */ - public static HttpRequest patch(final String destination) { - return new HttpRequest() - .method(HttpMethod.PATCH) - .set(destination); - } - /** - * Builds a DELETE request. - */ - public static HttpRequest delete(final String destination) { - return new HttpRequest() - .method(HttpMethod.DELETE) - .set(destination); - } - /** - * Builds a HEAD request. - */ - public static HttpRequest head(final String destination) { - return new HttpRequest() - .method(HttpMethod.HEAD) - .set(destination); - } - /** - * Builds a TRACE request. - */ - public static HttpRequest trace(final String destination) { - return new HttpRequest() - .method(HttpMethod.TRACE) - .set(destination); - } - /** - * Builds an OPTIONS request. - */ - public static HttpRequest options(final String destination) { - return new HttpRequest() - .method(HttpMethod.OPTIONS) - .set(destination); - } - - // ---------------------------------------------------------------- request - - /** - * Returns request method. - */ - public String method() { - return method; - } - - /** - * Specifies request method. It will be converted into uppercase. - * Does not validate if method is one of the HTTP methods. - */ - public HttpRequest method(final String method) { - this.method = method.toUpperCase(); - return this; - } - public HttpRequest method(final HttpMethod httpMethod) { - this.method = httpMethod.name(); - return this; - } - - /** - * Returns request path, without the query. - */ - public String path() { - return path; - } - - /** - * Sets request path. Query string is allowed. - * Adds a slash if path doesn't start with one. - * Query will be stripped out from the path. - * Previous query is discarded. - * @see #query() - */ - public HttpRequest path(String path) { - // this must be the only place that sets the path - - if (!path.startsWith(StringPool.SLASH)) { - path = StringPool.SLASH + path; - } - - final int ndx = path.indexOf('?'); - - if (ndx != -1) { - final String queryString = path.substring(ndx + 1); - - path = path.substring(0, ndx); - - query = HttpUtil.parseQuery(queryString, true); - } else { - query = HttpMultiMap.newCaseInsensitiveMap(); - } - - this.path = path; - - return this; - } - - /** - * Forces multipart requests. When set to false, - * it will be {@link #isFormMultipart() detected} if request - * should be multipart. By setting this to true - * we are forcing usage of multipart request. - */ - public HttpRequest multipart(final boolean multipart) { - this.multipart = multipart; - return this; - } - - - // ---------------------------------------------------------------- cookies - - /** - * Sets cookies to the request. - */ - public HttpRequest cookies(final Cookie... cookies) { - if (cookies.length == 0) { - return this; - } - - final StringBuilder cookieString = new StringBuilder(); - - boolean first = true; - - for (final Cookie cookie : cookies) { - final Integer maxAge = cookie.getMaxAge(); - if (maxAge != null && maxAge.intValue() == 0) { - continue; - } - - if (!first) { - cookieString.append("; "); - } - - first = false; - cookieString.append(cookie.getName()); - cookieString.append('='); - cookieString.append(cookie.getValue()); - } - - headerOverwrite("cookie", cookieString.toString()); - - return this; - } - - - // ---------------------------------------------------------------- query - - /** - * Adds query parameter. - */ - public HttpRequest query(final String name, final String value) { - query.add(name, value); - return this; - } - - /** - * Adds many query parameters at once. Although it accepts objects, - * each value will be converted to string. - */ - public HttpRequest query(final String name1, final Object value1, final Object... parameters) { - query(name1, value1 == null ? null : value1.toString()); - - for (int i = 0; i < parameters.length; i += 2) { - final String name = parameters[i].toString(); - - final String value = parameters[i + 1].toString(); - query.add(name, value); - } - return this; - } - - /** - * Adds all parameters from the provided map. - */ - public HttpRequest query(final Map queryMap) { - for (final Map.Entry entry : queryMap.entrySet()) { - query.add(entry.getKey(), entry.getValue()); - } - return this; - } - - /** - * Returns backend map of query parameters. - */ - public HttpMultiMap query() { - return query; - } - - /** - * Clears all query parameters. - */ - public HttpRequest clearQueries() { - query.clear(); - return this; - } - - /** - * Removes query parameters for given name. - */ - public HttpRequest queryRemove(final String name) { - query.remove(name); - return this; - } - - // ---------------------------------------------------------------- queryString - - /** - * @see #queryString(String, boolean) - */ - public HttpRequest queryString(final String queryString) { - return queryString(queryString, true); - } - - /** - * Sets query from provided query string. Previous query values - * are discarded. - */ - public HttpRequest queryString(final String queryString, final boolean decode) { - this.query = HttpUtil.parseQuery(queryString, decode); - return this; - } - - /** - * Generates query string. All values are URL encoded. - */ - public String queryString() { - if (query == null) { - return StringPool.EMPTY; - } - return HttpUtil.buildQuery(query, queryEncoding); - } - - // ---------------------------------------------------------------- query encoding - - protected String queryEncoding = Defaults.queryEncoding; - - /** - * Defines encoding for query parameters. - */ - public HttpRequest queryEncoding(final String encoding) { - this.queryEncoding = encoding; - return this; - } - - // ---------------------------------------------------------------- full path - - /** - * Returns full URL path. - * Simply concatenates {@link #protocol(String) protocol}, {@link #host(String) host}, - * {@link #port(int) port}, {@link #path(String) path} and {@link #queryString(String) query string}. - */ - public String url() { - final StringBuilder url = new StringBuilder(); - - url.append(hostUrl()); - - if (path != null) { - url.append(path); - } - - final String queryString = queryString(); - - if (StringUtil.isNotBlank(queryString)) { - url.append('?'); - url.append(queryString); - } - - return url.toString(); - } - - /** - * Returns just host url, without path and query. - */ - public String hostUrl() { - final StringBand url = new StringBand(8); - - if (protocol != null) { - url.append(protocol); - url.append("://"); - } - - if (host != null) { - url.append(host); - } - - if (port != Defaults.DEFAULT_PORT) { - url.append(':'); - url.append(port); - } - - return url.toString(); - } - - // ---------------------------------------------------------------- auth - - /** - * Enables basic authentication by adding required header. - */ - public HttpRequest basicAuthentication(final String username, final String password) { - if (username != null && password != null) { - final String data = username.concat(StringPool.COLON).concat(password); - - final String base64 = Base64.encodeToString(data); - - headerOverwrite(HEADER_AUTHORIZATION, "Basic " + base64); - } - - return this; - } - - /** - * Enables token-based authentication. - */ - public HttpRequest tokenAuthentication(final String token) { - if (token != null) { - headerOverwrite(HEADER_AUTHORIZATION, "Bearer " + token); - } - return this; - } - - - // ---------------------------------------------------------------- https - - private boolean trustAllCertificates; - private boolean verifyHttpsHost = true; - - /** - * Trusts all certificates, use with caution. - */ - public HttpRequest trustAllCerts(final boolean trust) { - trustAllCertificates = trust; - return this; - } - - /** - * Returns a flag if to trusts all certificates. - */ - public boolean trustAllCertificates() { - return trustAllCertificates; - } - - /** - * Verifies HTTPS hosts. - */ - public HttpRequest verifyHttpsHost(final boolean verifyHttpsHost) { - this.verifyHttpsHost = verifyHttpsHost; - return this; - } - - /** - * Returns a flag if to verify https hosts. - */ - public boolean verifyHttpsHost() { - return verifyHttpsHost; - } - - // ---------------------------------------------------------------- misc - - /** - * Sets 'Host' header from current host and port. - */ - public HttpRequest setHostHeader() { - String hostPort = this.host; - - if (port != Defaults.DEFAULT_PORT) { - hostPort += StringPool.COLON + port; - } - - headerOverwrite(HEADER_HOST, hostPort); - return this; - } - - // ---------------------------------------------------------------- monitor - - /** - * Registers {@link jodd.http.HttpProgressListener listener} that will - * monitor upload progress. Be aware that the whole size of the - * request is being monitored, not only the files content. - */ - public HttpRequest monitor(final HttpProgressListener httpProgressListener) { - this.httpProgressListener = httpProgressListener; - return this; - } - - // ---------------------------------------------------------------- connection properties - - protected int timeout = -1; - protected int connectTimeout = -1; - protected boolean followRedirects = false; - protected int maxRedirects = 50; - - /** - * Defines the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout for waiting for data or, - * put differently, a maximum period inactivity between two consecutive data packets). - * After establishing the connection, the client socket waits for response after sending - * the request. This is the elapsed time since the client has sent request to the - * server before server responds. Please note that this is not same as HTTP Error 408 which - * the server sends to the client. In other words its maximum period inactivity between - * two consecutive data packets arriving at client side after connection is established. - * A timeout value of zero is interpreted as an infinite timeout. - * @see jodd.http.HttpConnection#setTimeout(int) - */ - public HttpRequest timeout(final int milliseconds) { - this.timeout = milliseconds; - return this; - } - - /** - * Returns read timeout (SO_TIMEOUT) in milliseconds. Negative value - * means that default value is used. - * @see #timeout(int) - */ - public int timeout() { - return timeout; - } - - /** - * Defines the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout - * for waiting for data or, put differently, a maximum period inactivity between - * two consecutive data packets). A timeout value of zero is interpreted as - * an infinite timeout. - */ - public HttpRequest connectionTimeout(final int milliseconds) { - this.connectTimeout = milliseconds; - return this; - } - - /** - * Returns socket connection timeout. Negative value means that default - * value is used. - * @see #connectionTimeout(int) - */ - public int connectionTimeout() { - return connectTimeout; - } - - /** - * Defines if redirects responses should be followed. NOTE: when redirection is enabled, - * the original URL will NOT be preserved in the request! - */ - public HttpRequest followRedirects(final boolean followRedirects) { - this.followRedirects = followRedirects; - return this; - } - - /** - * Returns {@code true} if redirects are followed. - */ - public boolean isFollowRedirects() { - return this.followRedirects; - } - - /** - * Sets the max number of redirects, used when {@link #followRedirects} is enabled. - */ - public HttpRequest maxRedirects(final int maxRedirects) { - this.maxRedirects = maxRedirects; - return this; - } - - /** - * Returns max number of redirects, used when {@link #followRedirects} is enabled. - */ - public int maxRedirects() { - return this.maxRedirects; - } - - - // ---------------------------------------------------------------- send - - protected HttpConnection httpConnection; - protected HttpConnectionProvider httpConnectionProvider; - - /** - * Uses custom connection provider when {@link #open() opening} the - * connection. - */ - public HttpRequest withConnectionProvider(final HttpConnectionProvider httpConnectionProvider) { - this.httpConnectionProvider = httpConnectionProvider; - return this; - } - - /** - * Returns http connection provider that was used for creating - * current http connection. If null, default - * connection provider will be used. - */ - public HttpConnectionProvider connectionProvider() { - return httpConnectionProvider; - } - - /** - * Returns {@link HttpConnection} that is going to be - * used for sending this request. Value is available - * ONLY after calling {@link #open()} and before {@link #send()}. - */ - public HttpConnection connection() { - return httpConnection; - } - - /** - * Opens a new {@link HttpConnection connection} using either - * provided or {@link HttpConnectionProvider default} connection - * provider. - */ - public HttpRequest open() { - if (httpConnectionProvider == null) { - return open(HttpConnectionProvider.get()); - } - - return open(httpConnectionProvider); - } - - /** - * Opens a new {@link jodd.http.HttpConnection connection} - * using given {@link jodd.http.HttpConnectionProvider}. - */ - public HttpRequest open(final HttpConnectionProvider httpConnectionProvider) { - if (this.httpConnection != null) { - throw new HttpException("Connection already opened"); - } - try { - this.httpConnectionProvider = httpConnectionProvider; - this.httpConnection = httpConnectionProvider.createHttpConnection(this); - } catch (final IOException ioex) { - throw new HttpException("Can't connect to: " + url(), ioex); - } - - return this; - } - - /** - * Assignees provided {@link jodd.http.HttpConnection} for communication. - * It does not actually opens it until the {@link #send() sending}. - */ - public HttpRequest open(final HttpConnection httpConnection) { - if (this.httpConnection != null) { - throw new HttpException("Connection already opened"); - } - this.httpConnection = httpConnection; - this.httpConnectionProvider = null; - return this; - } - - /** - * Continues using the same keep-alive connection. - * Don't use any variant of open() when - * continuing the communication! - * First it checks if "Connection" header exist in the response - * and if it is equal to "Keep-Alive" value. Then it - * checks the "Keep-Alive" headers "max" parameter. - * If its value is positive, then the existing {@link jodd.http.HttpConnection} - * from the request will be reused. If max value is 1, - * connection will be sent with "Connection: Close" header, indicating - * its the last request. When new connection is created, the - * same {@link jodd.http.HttpConnectionProvider} that was used for - * creating initial connection is used for opening the new connection. - * - * @param doContinue set it to false to indicate the last connection - */ - public HttpRequest keepAlive(final HttpResponse httpResponse, final boolean doContinue) { - boolean keepAlive = httpResponse.isConnectionPersistent(); - if (keepAlive) { - final HttpConnection previousConnection = httpResponse.getHttpRequest().httpConnection; - - if (previousConnection != null) { - // keep using the connection! - this.httpConnection = previousConnection; - this.httpConnectionProvider = httpResponse.getHttpRequest().connectionProvider(); - } - - //keepAlive = true; (already set) - } else { - // close previous connection - httpResponse.close(); - - // force keep-alive on new request - keepAlive = true; - } - - // if we don't want to continue with this persistent session, mark this connection as closed - if (!doContinue) { - keepAlive = false; - } - - connectionKeepAlive(keepAlive); - - // if connection is not opened, open it using previous connection provider - if (httpConnection == null) { - open(httpResponse.getHttpRequest().connectionProvider()); - } - return this; - } - - /** - * {@link #open() Opens connection} if not already open, sends request, - * reads response and closes the request. If keep-alive mode is enabled - * connection will not be closed. - */ - public HttpResponse send() { - if (!followRedirects) { - return _send(); - } - - int redirects = this.maxRedirects; - - while (redirects > 0) { - redirects--; - - final HttpResponse httpResponse = _send(); - - final int statusCode = httpResponse.statusCode(); - - if (HttpStatus.isRedirect(statusCode)) { - _reset(); - set(httpResponse.location()); - continue; - } - - return httpResponse; - } - - throw new HttpException("Max number of redirects exceeded: " + this.maxRedirects); - } - - /** - * Resets the request by resetting all additional values - * added during the sending. - */ - private void _reset() { - headers.remove(HEADER_HOST); - } - - private HttpResponse _send() { - if (httpConnection == null) { - open(); - } - - // sends data - final HttpResponse httpResponse; - try { - final OutputStream outputStream = httpConnection.getOutputStream(); - - sendTo(outputStream); - - final InputStream inputStream = httpConnection.getInputStream(); - - httpResponse = HttpResponse.readFrom(inputStream); - - httpResponse.assignHttpRequest(this); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - final boolean keepAlive = httpResponse.isConnectionPersistent(); - - if (!keepAlive) { - // closes connection if keep alive is false, or if counter reached 0 - httpConnection.close(); - httpConnection = null; - } - - return httpResponse; - } - - // ---------------------------------------------------------------- buffer - - /** - * Prepares the request buffer. - */ - @Override - protected Buffer buffer(final boolean fullRequest) { - // INITIALIZATION - - // host port - - if (header(HEADER_HOST) == null) { - setHostHeader(); - } - - // form - - final Buffer formBuffer = formBuffer(); - - // query string - - final String queryString = queryString(); - - // user-agent - - if (header("User-Agent") == null) { - header("User-Agent", Defaults.userAgent); - } - - // POST method requires Content-Type to be set - - if (method.equals("POST") && (contentLength() == null)) { - contentLength(0); - } - - - // BUILD OUT - - final Buffer request = new Buffer(); - - request.append(method) - .append(SPACE) - .append(path); - - if (query != null && !query.isEmpty()) { - request.append('?'); - request.append(queryString); - } - - request.append(SPACE) - .append(httpVersion) - .append(CRLF); - - populateHeaderAndBody(request, formBuffer, fullRequest); - - return request; - } - - // ---------------------------------------------------------------- parse - - /** - * Parses input stream and creates new HttpRequest object. - * Assumes input stream is in ISO_8859_1 encoding. - */ - public static HttpRequest readFrom(final InputStream in) { - return readFrom(in, StandardCharsets.ISO_8859_1.name()); - } - - public static HttpRequest readFrom(final InputStream in, final String encoding) { - final BufferedReader reader; - try { - reader = new BufferedReader(new InputStreamReader(in, encoding)); - } catch (final UnsupportedEncodingException uneex) { - return null; - } - - final HttpRequest httpRequest = new HttpRequest(); - httpRequest.headers.clear(); - - final String line; - try { - line = reader.readLine(); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - if (!StringUtil.isBlank(line)) { - final String[] s = StringUtil.splitc(line, ' '); - - httpRequest.method(s[0]); - httpRequest.path(s[1]); - httpRequest.httpVersion(s[2]); - - httpRequest.readHeaders(reader); - httpRequest.readBody(reader); - } - - return httpRequest; - } - - - // ---------------------------------------------------------------- shortcuts - - /** - * Specifies JSON content type. - */ - public HttpRequest contentTypeJson() { - return contentType(MimeTypes.MIME_APPLICATION_JSON); - } - - /** - * Accepts JSON content type. - */ - public HttpRequest acceptJson() { - return accept(MimeTypes.MIME_APPLICATION_JSON); - } - - - // ---------------------------------------------------------------- functional/async - - /** - * Sends http request asynchronously using common fork-join pool. - * Note that this is not the right non-blocking call (not a NIO), it is just - * a regular call that is operated in a separate thread. - */ - public CompletableFuture sendAsync() { - return CompletableFuture.supplyAsync(this::send); - } - - /** - * Syntax sugar. - */ - public R sendAndReceive(final Function responseHandler) { - return responseHandler.apply(send()); - } - - /** - * Syntax sugar. - */ - public void sendAndReceive(final Consumer responseHandler) { - responseHandler.accept(send()); - } - -} diff --git a/jodd-http/src/main/java/jodd/http/HttpResponse.java b/jodd-http/src/main/java/jodd/http/HttpResponse.java deleted file mode 100644 index d74905dfd..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpResponse.java +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.IOUtil; -import jodd.util.StringPool; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.GZIPInputStream; - -import static jodd.util.StringPool.CRLF; -import static jodd.util.StringPool.SPACE; - -/** - * HTTP response. - */ -public class HttpResponse extends HttpBase { - - protected int statusCode; - protected String statusPhrase; - - /** - * Returns response status code. - */ - public int statusCode() { - return statusCode; - } - - /** - * Sets response status code. - */ - public HttpResponse statusCode(final int statusCode) { - this.statusCode = statusCode; - return this; - } - - /** - * Returns response status phrase. - */ - public String statusPhrase() { - return statusPhrase; - } - - /** - * Sets response status phrase. - */ - public HttpResponse statusPhrase(final String statusPhrase) { - this.statusPhrase = statusPhrase; - return this; - } - - /** - * Parses 'location' header to return the next location or returns {@code null} if location not specified. - * Specification (rfc2616) - * says that only absolute path must be provided, however, this does not - * happens in the real world. There a proposal - * that allows server name etc to be omitted. - */ - public String location() { - String location = header("location"); - - if (location == null) { - return null; - } - - if (location.startsWith(StringPool.SLASH)) { - location = getHttpRequest().hostUrl() + location; - } - - return location; - } - - // ---------------------------------------------------------------- cookie - - /** - * Returns list of valid cookies sent from server. - * If no cookie found, returns an empty array. Invalid cookies are ignored. - */ - public Cookie[] cookies() { - final List newCookies = headers("set-cookie"); - - if (newCookies == null) { - return new Cookie[0]; - } - - final List cookieList = new ArrayList<>(newCookies.size()); - - for (final String cookieValue : newCookies) { - try { - final Cookie cookie = new Cookie(cookieValue); - - cookieList.add(cookie); - } - catch (final Exception ex) { - // ignore - } - } - - return cookieList.toArray(new Cookie[0]); - } - - // ---------------------------------------------------------------- body - - /** - * Unzips GZip-ed body content, removes the content-encoding header - * and sets the new content-length value. - */ - public HttpResponse unzip() { - final String contentEncoding = contentEncoding(); - - if (contentEncoding != null && contentEncoding().equals("gzip")) { - if (body != null) { - headerRemove(HEADER_CONTENT_ENCODING); - try { - final ByteArrayInputStream in = new ByteArrayInputStream(body.getBytes(StandardCharsets.ISO_8859_1)); - final GZIPInputStream gzipInputStream = new GZIPInputStream(in); - - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - - IOUtil.copy(gzipInputStream, out); - - body(out.toString(StandardCharsets.ISO_8859_1.name())); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - } - } - return this; - } - - // ---------------------------------------------------------------- buffer - - - /** - * Creates response {@link jodd.http.Buffer buffer}. - */ - @Override - protected Buffer buffer(final boolean fullResponse) { - // form - - final Buffer formBuffer = formBuffer(); - - // response - - final Buffer response = new Buffer(); - - response.append(httpVersion) - .append(SPACE) - .append(statusCode) - .append(SPACE) - .append(statusPhrase) - .append(CRLF); - - populateHeaderAndBody(response, formBuffer, fullResponse); - - return response; - } - - // ---------------------------------------------------------------- read from - - /** - * Reads response input stream and returns {@link HttpResponse response}. - * Supports both streamed and chunked response. - */ - public static HttpResponse readFrom(final InputStream in) { - final InputStreamReader inputStreamReader = new InputStreamReader(in, StandardCharsets.ISO_8859_1); - final BufferedReader reader = new BufferedReader(inputStreamReader); - - final HttpResponse httpResponse = new HttpResponse(); - - // the first line - String line; - try { - line = reader.readLine(); - } catch (final IOException ioex) { - throw new HttpException(ioex); - } - - if (line != null) { - - line = line.trim(); - - int ndx = line.indexOf(' '); - int ndx2; - - if (ndx > -1) { - httpResponse.httpVersion(line.substring(0, ndx)); - - ndx2 = line.indexOf(' ', ndx + 1); - } - else { - httpResponse.httpVersion(HTTP_1_1); - ndx2 = -1; - ndx = 0; - } - - if (ndx2 == -1) { - ndx2 = line.length(); - } - - try { - httpResponse.statusCode(Integer.parseInt(line.substring(ndx, ndx2).trim())); - } - catch (final NumberFormatException nfex) { - httpResponse.statusCode(-1); - } - - httpResponse.statusPhrase(line.substring(ndx2).trim()); - } - - httpResponse.readHeaders(reader); - httpResponse.readBody(reader); - - return httpResponse; - } - - // ---------------------------------------------------------------- request - - protected HttpRequest httpRequest; - - /** - * Binds {@link jodd.http.HttpRequest} to this response. - */ - void assignHttpRequest(final HttpRequest httpRequest) { - this.httpRequest = httpRequest; - } - - /** - * Returns {@link jodd.http.HttpRequest} that created this response. - */ - public HttpRequest getHttpRequest() { - return httpRequest; - } - - /** - * Closes requests connection if it was open. - * Should be called when using keep-alive connections. - * Otherwise, connection will be already closed. - */ - public HttpResponse close() { - final HttpConnection httpConnection = httpRequest.httpConnection; - if (httpConnection != null) { - httpConnection.close(); - httpRequest.httpConnection = null; - } - return this; - } - -} diff --git a/jodd-http/src/main/java/jodd/http/HttpStatus.java b/jodd-http/src/main/java/jodd/http/HttpStatus.java deleted file mode 100644 index 621bf200a..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpStatus.java +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -package jodd.http; - -/** - * HTTP status codes. - */ -public class HttpStatus { - - /** - * Returns {@code true} if status code indicates successful result. - */ - public static boolean isSuccessful(final int statusCode) { - return statusCode < 400; - } - - /** - * Returns {@code true} if status code indicates a redirect. - */ - public static boolean isRedirect(final int statusCode) { - return statusCode >=300 && statusCode < 400; - } - - /** - * Returns {@code true} if status code indicates an error. - */ - public static boolean isError(final int statusCode) { - return statusCode >= 500; - } - - // ---------------------------------------------------------------- 1xx - - /** - * HTTP Status-Code 100: Continue. - */ - public static final int HTTP_CONTINUE = 100; - - // ---------------------------------------------------------------- 2xx - - /** - * HTTP Status-Code 200: OK. - */ - public static final int HTTP_OK = 200; - - /** - * HTTP Status-Code 201: Created. - */ - public static final int HTTP_CREATED = 201; - - /** - * HTTP Status-Code 202: Accepted. - */ - public static final int HTTP_ACCEPTED = 202; - - /** - * HTTP Status-Code 203: Non-Authoritative Information. - */ - public static final int HTTP_NOT_AUTHORITATIVE = 203; - - /** - * HTTP Status-Code 204: No Content. - */ - public static final int HTTP_NO_CONTENT = 204; - - /** - * HTTP Status-Code 205: Reset Content. - */ - public static final int HTTP_RESET = 205; - - /** - * HTTP Status-Code 206: Partial Content. - */ - public static final int HTTP_PARTIAL = 206; - - // ---------------------------------------------------------------- 3xx - - /** - * HTTP Status-Code 300: Multiple Choices. - */ - public static final int HTTP_MULTIPLE_CHOICES = 300; - - /** - * HTTP Status-Code 301: Moved Permanently. - */ - public static final int HTTP_MOVED_PERMANENTLY = 301; - - /** - * HTTP Status-Code 302: Temporary Redirect. - */ - public static final int HTTP_MOVED_TEMPORARY = 302; - - /** - * HTTP Status-Code 303: See Other. - */ - public static final int HTTP_SEE_OTHER = 303; - - /** - * HTTP Status-Code 304: Not Modified. - */ - public static final int HTTP_NOT_MODIFIED = 304; - - /** - * HTTP Status-Code 305: Use Proxy. - */ - public static final int HTTP_USE_PROXY = 305; - - /** - * HTTP Status-Code 307: Temporary Redirect. - */ - public static final int HTTP_TEMPORARY_REDIRECT = 307; - - // ---------------------------------------------------------------- 4xx - - /** - * HTTP Status-Code 400: Bad Request. - */ - public static final int HTTP_BAD_REQUEST = 400; - - /** - * HTTP Status-Code 401: Unauthorized. - */ - public static final int HTTP_UNAUTHORIZED = 401; - - /** - * HTTP Status-Code 402: Payment Required. - */ - public static final int HTTP_PAYMENT_REQUIRED = 402; - - /** - * HTTP Status-Code 403: Forbidden. - */ - public static final int HTTP_FORBIDDEN = 403; - - /** - * HTTP Status-Code 404: Not Found. - */ - public static final int HTTP_NOT_FOUND = 404; - - /** - * HTTP Status-Code 405: Method Not Allowed. - */ - public static final int HTTP_BAD_METHOD = 405; - - /** - * HTTP Status-Code 406: Not Acceptable. - */ - public static final int HTTP_NOT_ACCEPTABLE = 406; - - /** - * HTTP Status-Code 407: Proxy Authentication Required. - */ - public static final int HTTP_PROXY_AUTH_REQUIRED = 407; - - /** - * HTTP Status-Code 408: Request Time-Out. - */ - public static final int HTTP_CLIENT_TIMEOUT = 408; - - /** - * HTTP Status-Code 409: Conflict. - */ - public static final int HTTP_CONFLICT = 409; - - /** - * HTTP Status-Code 410: Gone. - */ - public static final int HTTP_GONE = 410; - - /** - * HTTP Status-Code 411: Length Required. - */ - public static final int HTTP_LENGTH_REQUIRED = 411; - - /** - * HTTP Status-Code 412: Precondition Failed. - */ - public static final int HTTP_PRECON_FAILED = 412; - - /** - * HTTP Status-Code 413: Request Entity Too Large. - */ - public static final int HTTP_ENTITY_TOO_LARGE = 413; - - /** - * HTTP Status-Code 414: Request-URI Too Large. - */ - public static final int HTTP_REQ_TOO_LONG = 414; - - /** - * HTTP Status-Code 415: Unsupported Media Type. - */ - public static final int HTTP_UNSUPPORTED_TYPE = 415; - - // ---------------------------------------------------------------- 5xx - - /** - * HTTP Status-Code 500: Internal Server Error. - */ - public static final int HTTP_INTERNAL_ERROR = 500; - - /** - * HTTP Status-Code 501: Not Implemented. - */ - public static final int HTTP_NOT_IMPLEMENTED = 501; - - /** - * HTTP Status-Code 502: Bad Gateway. - */ - public static final int HTTP_BAD_GATEWAY = 502; - - /** - * HTTP Status-Code 503: Service Unavailable. - */ - public static final int HTTP_UNAVAILABLE = 503; - - /** - * HTTP Status-Code 504: Gateway Timeout. - */ - public static final int HTTP_GATEWAY_TIMEOUT = 504; - - /** - * HTTP Status-Code 505: HTTP Version Not Supported. - */ - public static final int HTTP_VERSION_NOT_SUPPORTED = 505; - -} diff --git a/jodd-http/src/main/java/jodd/http/HttpTunnel.java b/jodd-http/src/main/java/jodd/http/HttpTunnel.java deleted file mode 100644 index bf7365e80..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpTunnel.java +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.IOUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Simple HTTP tunnel base ready to be extended. - */ -public class HttpTunnel { - - /** - * The number of threads that can be executed in parallel. - */ - protected int threadPoolSize = 10; - - /** - * Number of incoming sockets connection that can be hold - * before processing each. - */ - protected int socketBacklog = 100; - - /** - * Tunnel listening port. - */ - protected int listenPort = 8888; - - /** - * Target host. - */ - protected String targetHost = "localhost"; - - /** - * Target port. - */ - protected int targetPort = 8080; - - protected ExecutorService executorService; - protected volatile boolean running; - protected ServerSocket serverSocket; - - /** - * Starts HTTP tunnel. Method ends when the tunnel is stopped. - */ - public void start() throws IOException { - serverSocket = new ServerSocket(listenPort, socketBacklog); - serverSocket.setReuseAddress(true); - executorService = Executors.newFixedThreadPool(threadPoolSize); - - running = true; - while (running) { - final Socket socket = serverSocket.accept(); - socket.setKeepAlive(false); - executorService.execute(onSocketConnection(socket)); - } - executorService.shutdown(); - } - - /** - * Invoked on incoming connection. By default returns {@link HttpTunnelConnection} - * to handle the connection. May be used to return custom - * handlers. - */ - protected Runnable onSocketConnection(final Socket socket) { - return new HttpTunnelConnection(socket); - } - - /** - * Stops the tunnel, shutdowns the thread pool and closes server socket. - */ - public void stop() { - running = false; - executorService.shutdown(); - try { - serverSocket.close(); - } catch (final IOException ignore) { - } - } - - /** - * Single connection handler that performs the tunneling. - */ - public class HttpTunnelConnection implements Runnable { - - protected final Socket socket; - - public HttpTunnelConnection(final Socket socket) { - this.socket = socket; - } - - @Override - public void run() { - try { - tunnel(); - } catch (final IOException ioex) { - ioex.printStackTrace(); - } - } - - /** - * Invoked after income connection is parsed. Nothing is - * changed in the request, except the target host and port. - */ - protected void onRequest(final HttpRequest request) { - } - - /** - * Invoked after target response is processed. Response is now - * ready to be sent back to the client. The following header - * parameters are changed: - *
    - *
  • Transfer-Encoding is removed, as body is returned at once,
  • - *
  • Content-Length is added/update to body size.
  • - *
- */ - protected void onResponse(final HttpResponse response) { - } - - /** - * Performs the tunneling. The following steps occurs: - *
    - *
  • read and parse clients request
  • - *
  • open socket to target
  • - *
  • resend request to target
  • - *
  • read targets response
  • - *
  • fix response and resend it to client
  • - *
- */ - protected void tunnel() throws IOException { - - // read request - final InputStream socketInput = socket.getInputStream(); - final HttpRequest request = HttpRequest.readFrom(socketInput); - - // open client socket to target - final Socket clientSocket = Sockets.connect(targetHost, targetPort); - - // do request - request.host(targetHost); - request.port(targetPort); - request.setHostHeader(); - onRequest(request); - - // resend request to target - final OutputStream out = clientSocket.getOutputStream(); - request.sendTo(out); - - // read target response - final InputStream in = clientSocket.getInputStream(); - final HttpResponse response = HttpResponse.readFrom(in); - - // close client socket - IOUtil.close(in); - IOUtil.close(out); - try { - clientSocket.close(); - } catch (final IOException ignore) { - } - - // fix response - if (response.body() != null) { - response.headerRemove("Transfer-Encoding"); - response.contentLength(response.body().length()); - } - - // do response - onResponse(response); - - // send response back - final OutputStream socketOutput = socket.getOutputStream(); - response.sendTo(socketOutput); - - // close socket - IOUtil.close(socketInput); - IOUtil.close(socketOutput); - try { - socket.close(); - } catch (final IOException ignore) { - } - } - } - -} diff --git a/jodd-http/src/main/java/jodd/http/HttpUtil.java b/jodd-http/src/main/java/jodd/http/HttpUtil.java deleted file mode 100644 index 33a5539ae..000000000 --- a/jodd-http/src/main/java/jodd/http/HttpUtil.java +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.net.URLCoder; -import jodd.net.URLDecoder; -import jodd.util.StringBand; -import jodd.util.StringPool; -import jodd.util.StringUtil; - -import java.nio.charset.Charset; -import java.util.Map; - -/** - * Few HTTP utilities. - */ -public class HttpUtil { - - // ---------------------------------------------------------------- query - - /** - * Builds a query string from given query map. - */ - public static String buildQuery(final HttpMultiMap queryMap, final String encoding) { - if (queryMap.isEmpty()) { - return StringPool.EMPTY; - } - - final int queryMapSize = queryMap.size(); - - final StringBand query = new StringBand(queryMapSize * 4); - - int count = 0; - for (final Map.Entry entry : queryMap) { - String key = entry.getKey(); - key = URLCoder.encodeQueryParam(key, Charset.forName(encoding)); - - final Object value = entry.getValue(); - - if (value == null) { - if (count != 0) { - query.append('&'); - } - - query.append(key); - count++; - } else { - if (count != 0) { - query.append('&'); - } - - query.append(key); - count++; - query.append('='); - - final String valueString = URLCoder.encodeQueryParam(value.toString(), Charset.forName(encoding)); - query.append(valueString); - } - } - - return query.toString(); - } - - /** - * Parses query from give query string. Values are optionally decoded. - */ - public static HttpMultiMap parseQuery(final String query, final boolean decode) { - - final HttpMultiMap queryMap = HttpMultiMap.newCaseInsensitiveMap(); - - if (StringUtil.isBlank(query)) { - return queryMap; - } - - int lastNdx = 0; - while (lastNdx < query.length()) { - int ndx = query.indexOf('&', lastNdx); - if (ndx == -1) { - ndx = query.length(); - } - - final String paramAndValue = query.substring(lastNdx, ndx); - - ndx = paramAndValue.indexOf('='); - - if (ndx == -1) { - queryMap.add(paramAndValue, null); - } - else { - String name = paramAndValue.substring(0, ndx); - if (decode) { - name = URLDecoder.decodeQuery(name); - } - - String value = paramAndValue.substring(ndx + 1); - - if (decode) { - value = URLDecoder.decodeQuery(value); - } - - queryMap.add(name, value); - } - lastNdx += paramAndValue.length() + 1; - } - - return queryMap; - } - - // ---------------------------------------------------------------- misc - - /** - * Makes nice header names. - */ - public static String prepareHeaderParameterName(final String headerName) { - - // special cases - - if (headerName.equals("etag")) { - return HttpBase.HEADER_ETAG; - } - - if (headerName.equals("www-authenticate")) { - return "WWW-Authenticate"; - } - - final char[] name = headerName.toCharArray(); - - boolean capitalize = true; - - for (int i = 0; i < name.length; i++) { - final char c = name[i]; - - if (c == '-') { - capitalize = true; - continue; - } - - if (capitalize) { - name[i] = Character.toUpperCase(c); - capitalize = false; - } else { - name[i] = Character.toLowerCase(c); - } - } - - return new String(name); - } - - // ---------------------------------------------------------------- content type - - /** - * Extracts media-type from value of "Content Type" header. - */ - public static String extractMediaType(final String contentType) { - final int index = contentType.indexOf(';'); - - if (index == -1) { - return contentType; - } - - return contentType.substring(0, index); - } - - /** - * @see #extractHeaderParameter(String, String, char) - */ - public static String extractContentTypeCharset(final String contentType) { - return extractHeaderParameter(contentType, "charset", ';'); - } - - // ---------------------------------------------------------------- keep-alive - - /** - * Extract keep-alive timeout. - */ - public static String extractKeepAliveTimeout(final String keepAlive) { - return extractHeaderParameter(keepAlive, "timeout", ','); - } - - public static String extractKeepAliveMax(final String keepAlive) { - return extractHeaderParameter(keepAlive, "max", ','); - } - - // ---------------------------------------------------------------- header - - /** - * Extracts header parameter. Returns null - * if parameter not found. - */ - public static String extractHeaderParameter(final String header, final String parameter, final char separator) { - int index = 0; - - while (true) { - index = header.indexOf(separator, index); - - if (index == -1) { - return null; - } - - index++; - - // skip whitespaces - while (index < header.length() && header.charAt(index) == ' ') { - index++; - } - - int eqNdx = header.indexOf('=', index); - - if (eqNdx == -1) { - return null; - } - - final String paramName = header.substring(index, eqNdx); - - eqNdx++; - - if (!paramName.equalsIgnoreCase(parameter)) { - index = eqNdx; - continue; - } - - final int endIndex = header.indexOf(';', eqNdx); - - if (endIndex == -1) { - return header.substring(eqNdx); - } else { - return header.substring(eqNdx, endIndex); - } - } - } - -} diff --git a/jodd-http/src/main/java/jodd/http/ProxyInfo.java b/jodd-http/src/main/java/jodd/http/ProxyInfo.java deleted file mode 100644 index 7d04a05ae..000000000 --- a/jodd-http/src/main/java/jodd/http/ProxyInfo.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -/** - * Proxy information. - */ -public class ProxyInfo { - - /** - * Proxy types. - */ - public enum ProxyType { - NONE, HTTP, SOCKS4, SOCKS5 - } - - private final String proxyAddress; - private final int proxyPort; - private final String proxyUsername; - private final String proxyPassword; - private final ProxyType proxyType; - - public ProxyInfo(final ProxyType proxyType, final String proxyHost, final int proxyPort, final String proxyUser, final String proxyPassword) { - this.proxyType = proxyType; - this.proxyAddress = proxyHost; - this.proxyPort = proxyPort; - this.proxyUsername = proxyUser; - this.proxyPassword = proxyPassword; - } - - // ---------------------------------------------------------------- factory - - /** - * Creates directProxy. - */ - public static ProxyInfo directProxy() { - return new ProxyInfo(ProxyType.NONE, null, 0, null, null); - } - - /** - * Creates SOCKS4 proxy. - */ - public static ProxyInfo socks4Proxy(final String proxyAddress, final int proxyPort, final String proxyUser) { - return new ProxyInfo(ProxyType.SOCKS4, proxyAddress, proxyPort, proxyUser, null); - } - - /** - * Creates SOCKS5 proxy. - */ - public static ProxyInfo socks5Proxy(final String proxyAddress, final int proxyPort, final String proxyUser, final String proxyPassword) { - return new ProxyInfo(ProxyType.SOCKS5, proxyAddress, proxyPort, proxyUser, proxyPassword); - } - - /** - * Creates HTTP proxy. - */ - public static ProxyInfo httpProxy(final String proxyAddress, final int proxyPort, final String proxyUser, final String proxyPassword) { - return new ProxyInfo(ProxyType.HTTP, proxyAddress, proxyPort, proxyUser, proxyPassword); - } - - // ---------------------------------------------------------------- getter - - /** - * Returns proxy type. - */ - public ProxyType getProxyType() { - return proxyType; - } - - /** - * Returns proxy address. - */ - public String getProxyAddress() { - return proxyAddress; - } - - /** - * Returns proxy port. - */ - public int getProxyPort() { - return proxyPort; - } - - /** - * Returns proxy user name or null if - * no authentication required. - */ - public String getProxyUsername() { - return proxyUsername; - } - - /** - * Returns proxy password or null. - */ - public String getProxyPassword() { - return proxyPassword; - } - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/Sockets.java b/jodd-http/src/main/java/jodd/http/Sockets.java deleted file mode 100644 index 3834f9b1d..000000000 --- a/jodd-http/src/main/java/jodd/http/Sockets.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; - -/** - * Sockets factory. - */ -public class Sockets { - - /** - * Creates a socket. - */ - public static Socket connect(final String hostname, final int port) throws IOException { - final Socket socket = new Socket(); - socket.connect(new InetSocketAddress(hostname, port)); - return socket; - } - - /** - * Creates a socket with a timeout. - */ - public static Socket connect(final String hostname, final int port, final int connectionTimeout) throws IOException { - final Socket socket = new Socket(); - if (connectionTimeout <= 0) { - socket.connect(new InetSocketAddress(hostname, port)); - } - else { - socket.connect(new InetSocketAddress(hostname, port), connectionTimeout); - } - return socket; - } -} diff --git a/jodd-http/src/main/java/jodd/http/net/HTTPProxySocketFactory.java b/jodd-http/src/main/java/jodd/http/net/HTTPProxySocketFactory.java deleted file mode 100644 index 2462310ac..000000000 --- a/jodd-http/src/main/java/jodd/http/net/HTTPProxySocketFactory.java +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.HttpException; -import jodd.http.ProxyInfo; -import jodd.http.Sockets; -import jodd.util.Base64; - -import javax.net.SocketFactory; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.StringReader; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.Socket; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Socket factory for HTTP proxy. - */ -public class HTTPProxySocketFactory extends SocketFactory { - - private final ProxyInfo proxy; - private final int connectionTimeout; - - public HTTPProxySocketFactory(final ProxyInfo proxy, final int connectionTimeout) { - this.proxy = proxy; - this.connectionTimeout = connectionTimeout; - } - - @Override - public Socket createSocket(final String host, final int port) { - return createHttpProxySocket(host, port); - } - - @Override - public Socket createSocket(final String host, final int port, final InetAddress localHost, final int localPort) { - return createHttpProxySocket(host, port); - } - - @Override - public Socket createSocket(final InetAddress host, final int port) { - return createHttpProxySocket(host.getHostAddress(), port); - } - - @Override - public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddress, final int localPort) { - return createHttpProxySocket(address.getHostAddress(), port); - } - - private Socket createHttpProxySocket(final String host, final int port) { - Socket socket = null; - final String proxyAddress = proxy.getProxyAddress(); - final int proxyPort = proxy.getProxyPort(); - - try { - socket = Sockets.connect(proxyAddress, proxyPort, connectionTimeout); - String hostport = host + ":" + port; - String proxyLine = ""; - String username = proxy.getProxyUsername(); - - if (username != null) { - String password = proxy.getProxyPassword(); - proxyLine = - "Proxy-Authorization: Basic " + - Base64.encodeToString((username + ":" + password)) + "\r\n"; - } - - socket.getOutputStream().write( - ("CONNECT " + hostport + " HTTP/1.1\r\n" + - "Host: " + hostport + "\r\n" + - proxyLine + - "\r\n" - ).getBytes("UTF-8") - ); - - InputStream in = socket.getInputStream(); - StringBuilder recv = new StringBuilder(100); - int nlchars = 0; - - do { - int i = in.read(); - if (i == -1) { - throw new HttpException(ProxyInfo.ProxyType.HTTP, "Invalid response"); - } - - char c = (char) i; - recv.append(c); - if (recv.length() > 1024) { - throw new HttpException(ProxyInfo.ProxyType.HTTP, "Received header longer then 1024 chars"); - } - if ((nlchars == 0 || nlchars == 2) && c == '\r') { - nlchars++; - } else if ((nlchars == 1 || nlchars == 3) && c == '\n') { - nlchars++; - } else { - nlchars = 0; - } - } while (nlchars != 4); - - String recvStr = recv.toString(); - - BufferedReader br = new BufferedReader(new StringReader(recvStr)); - String response = br.readLine(); - - if (response == null) { - throw new HttpException(ProxyInfo.ProxyType.HTTP, "Empty proxy response"); - } - - Matcher m = RESPONSE_PATTERN.matcher(response); - if (!m.matches()) { - throw new HttpException(ProxyInfo.ProxyType.HTTP, "Unexpected proxy response"); - } - - int code = Integer.parseInt(m.group(1)); - - if (code != HttpURLConnection.HTTP_OK) { - throw new HttpException(ProxyInfo.ProxyType.HTTP, "Invalid return status code: " + code); - } - - return socket; - } catch (RuntimeException rtex) { - closeSocket(socket); - throw rtex; - } catch (Exception ex) { - closeSocket(socket); - throw new HttpException(ProxyInfo.ProxyType.HTTP, ex.toString(), ex); - } - - } - - /** - * Closes socket silently. - */ - private void closeSocket(final Socket socket) { - try { - if (socket != null) { - socket.close(); - } - } catch (Exception ignore) { - } - } - - private static final Pattern RESPONSE_PATTERN = - Pattern.compile("HTTP/\\S+\\s(\\d+)\\s(.*)\\s*"); -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/net/SSLSocketHttpConnectionProvider.java b/jodd-http/src/main/java/jodd/http/net/SSLSocketHttpConnectionProvider.java deleted file mode 100644 index 23357cde1..000000000 --- a/jodd-http/src/main/java/jodd/http/net/SSLSocketHttpConnectionProvider.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.ProxyInfo; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; - -/** - * Custom SSL socket http connection provider. - */ -public class SSLSocketHttpConnectionProvider extends SocketHttpConnectionProvider { - - private final SSLSocketFactory socketFactory; - - public SSLSocketHttpConnectionProvider(final SSLSocketFactory sslSocketFactory) { - this.socketFactory = sslSocketFactory; - } - - public SSLSocketHttpConnectionProvider(final SSLContext sslContext) { - this.socketFactory = sslContext.getSocketFactory(); - } - - @Override - protected SocketFactory resolveSocketFactory( - final ProxyInfo proxy, - final boolean ssl, - final boolean trustAllCertificates, - final int connectionTimeout) { - return socketFactory; - } -} diff --git a/jodd-http/src/main/java/jodd/http/net/SocketHttpConnection.java b/jodd-http/src/main/java/jodd/http/net/SocketHttpConnection.java deleted file mode 100644 index 39f347f0b..000000000 --- a/jodd-http/src/main/java/jodd/http/net/SocketHttpConnection.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.HttpConnection; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -/** - * Socket-based {@link jodd.http.HttpConnection}. - * @see SocketHttpConnectionProvider - */ -public class SocketHttpConnection implements HttpConnection { - - protected final Socket socket; - - public SocketHttpConnection(final Socket socket) { - this.socket = socket; - } - - @Override - public void init() throws IOException { - if (timeout >= 0) { - socket.setSoTimeout(timeout); - } - } - - @Override - public OutputStream getOutputStream() throws IOException { - return socket.getOutputStream(); - } - - @Override - public InputStream getInputStream() throws IOException { - return socket.getInputStream(); - } - - @Override - public void close() { - try { - socket.close(); - } catch (Throwable ignore) { - } - } - - @Override - public void setTimeout(final int milliseconds) { - this.timeout = milliseconds; - } - - /** - * Returns Socket used by this connection. - */ - public Socket getSocket() { - return socket; - } - - private int timeout; -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/net/SocketHttpConnectionProvider.java b/jodd-http/src/main/java/jodd/http/net/SocketHttpConnectionProvider.java deleted file mode 100644 index 1c16cd596..000000000 --- a/jodd-http/src/main/java/jodd/http/net/SocketHttpConnectionProvider.java +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.HttpConnection; -import jodd.http.HttpConnectionProvider; -import jodd.http.HttpException; -import jodd.http.HttpRequest; -import jodd.http.ProxyInfo; -import jodd.http.Sockets; -import jodd.util.StringUtil; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -/** - * Socket factory for HTTP proxy. - */ -public class SocketHttpConnectionProvider implements HttpConnectionProvider { - - protected ProxyInfo proxy = ProxyInfo.directProxy(); - protected String secureEnabledProtocols = System.getProperty("https.protocols"); - protected String sslProtocol = "TLSv1.1"; - - /** - * Defines proxy to use for created sockets. - */ - @Override - public void useProxy(final ProxyInfo proxyInfo) { - proxy = proxyInfo; - } - - /** - * CSV of default enabled secured protocols. By default the value is - * read from system property https.protocols. - */ - public void setSecuredProtocols(final String secureEnabledProtocols) { - this.secureEnabledProtocols = secureEnabledProtocols; - } - - /** - * Returns current SSL protocol used. - */ - public String getSslProtocol() { - return sslProtocol; - } - - /** - * Sets default SSL protocol to use. One of "SSL", "TLSv1.2", "TLSv1.1", "TLSv1". - */ - public SocketHttpConnectionProvider setSslProtocol(final String sslProtocol) { - this.sslProtocol = sslProtocol; - return this; - } - - /** - * Creates new connection from current {@link jodd.http.HttpRequest request}. - * - * @see #createSocket(String, int, int) - */ - @Override - public HttpConnection createHttpConnection(final HttpRequest httpRequest) throws IOException { - final SocketHttpConnection httpConnection; - - final boolean https = httpRequest.protocol().equalsIgnoreCase("https"); - - if (https) { - SSLSocket sslSocket = createSSLSocket( - httpRequest.host(), - httpRequest.port(), - httpRequest.connectionTimeout(), - httpRequest.trustAllCertificates(), - httpRequest.verifyHttpsHost() - ); - - httpConnection = new SocketHttpSecureConnection(sslSocket); - } - else { - Socket socket = createSocket(httpRequest.host(), httpRequest.port(), httpRequest.connectionTimeout()); - - httpConnection = new SocketHttpConnection(socket); - } - - // prepare connection config - - httpConnection.setTimeout(httpRequest.timeout()); - - try { - // additional socket initialization - - httpConnection.init(); - } - catch (Throwable throwable) { // @wjw_add - httpConnection.close(); - - throw new HttpException(throwable); - } - - return httpConnection; - } - - /** - * Creates a socket using socket factory. - */ - protected Socket createSocket(final String host, final int port, final int connectionTimeout) throws IOException { - final SocketFactory socketFactory = resolveSocketFactory(proxy, false, false, connectionTimeout); - - if (connectionTimeout < 0) { - return socketFactory.createSocket(host, port); - } - else { - // creates unconnected socket - Socket socket = socketFactory.createSocket(); - - socket.connect(new InetSocketAddress(host, port), connectionTimeout); - - return socket; - } - } - - /** - * Creates a SSL socket. Enables default secure enabled protocols if specified. - */ - protected SSLSocket createSSLSocket( - final String host, final int port, final int connectionTimeout, - final boolean trustAll, final boolean verifyHttpsHost) throws IOException { - - final SocketFactory socketFactory = resolveSocketFactory(proxy, true, trustAll, connectionTimeout); - - final Socket socket; - - if (connectionTimeout < 0) { - socket = socketFactory.createSocket(host, port); - } - else { - // creates unconnected socket - // unfortunately, this does not work always - -// sslSocket = (SSLSocket) socketFactory.createSocket(); -// sslSocket.connect(new InetSocketAddress(host, port), connectionTimeout); - - // - // Note: SSLSocketFactory has several create() methods. - // Those that take arguments all connect immediately - // and have no options for specifying a connection timeout. - // - // So, we have to create a socket and connect it (with a - // connection timeout), then have the SSLSocketFactory wrap - // the already-connected socket. - // - socket = Sockets.connect(host, port, connectionTimeout); - //sock.setSoTimeout(readTimeout); - //socket.connect(new InetSocketAddress(host, port), connectionTimeout); - - // continue to wrap this plain socket with ssl socket... - } - - - // wrap plain socket in an SSL socket - - final SSLSocket sslSocket; - - if (socket instanceof SSLSocket) { - sslSocket = (SSLSocket) socket; - } - else { - if (socketFactory instanceof SSLSocketFactory) { - sslSocket = (SSLSocket) ((SSLSocketFactory)socketFactory).createSocket(socket, host, port, true); - } - else { - sslSocket = (SSLSocket) (getDefaultSSLSocketFactory(trustAll)).createSocket(socket, host, port, true); - } - } - - // sslSocket is now ready - - if (secureEnabledProtocols != null) { - final String[] values = StringUtil.splitc(secureEnabledProtocols, ','); - - StringUtil.trimAll(values); - - sslSocket.setEnabledProtocols(values); - } - - // set SSL parameters to allow host name verifier - - if (verifyHttpsHost) { - final SSLParameters sslParams = new SSLParameters(); - - sslParams.setEndpointIdentificationAlgorithm("HTTPS"); - - sslSocket.setSSLParameters(sslParams); - } - - return sslSocket; - } - - /** - * Returns default SSL socket factory allowing setting trust managers. - */ - protected SSLSocketFactory getDefaultSSLSocketFactory(final boolean trustAllCertificates) throws IOException { - if (trustAllCertificates) { - try { - SSLContext sc = SSLContext.getInstance(sslProtocol); - sc.init(null, TrustManagers.TRUST_ALL_CERTS, new java.security.SecureRandom()); - return sc.getSocketFactory(); - } - catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IOException(e); - } - } else { - return (SSLSocketFactory) SSLSocketFactory.getDefault(); - } - } - - /** - * Returns socket factory based on proxy type and SSL requirements. - */ - protected SocketFactory resolveSocketFactory( - final ProxyInfo proxy, - final boolean ssl, - final boolean trustAllCertificates, - final int connectionTimeout) throws IOException { - - switch (proxy.getProxyType()) { - case NONE: - if (ssl) { - return getDefaultSSLSocketFactory(trustAllCertificates); - } - else { - return SocketFactory.getDefault(); - } - case HTTP: - return new HTTPProxySocketFactory(proxy, connectionTimeout); - case SOCKS4: - return new Socks4ProxySocketFactory(proxy, connectionTimeout); - case SOCKS5: - return new Socks5ProxySocketFactory(proxy, connectionTimeout); - default: - throw new HttpException("Invalid proxy type " + proxy.getProxyType()); - } - } -} diff --git a/jodd-http/src/main/java/jodd/http/net/SocketHttpSecureConnection.java b/jodd-http/src/main/java/jodd/http/net/SocketHttpSecureConnection.java deleted file mode 100644 index b60ba3e2e..000000000 --- a/jodd-http/src/main/java/jodd/http/net/SocketHttpSecureConnection.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import javax.net.ssl.SSLSocket; -import java.io.IOException; - -public class SocketHttpSecureConnection extends SocketHttpConnection { - private final SSLSocket sslSocket; - - public SocketHttpSecureConnection(final SSLSocket socket) { - super(socket); - this.sslSocket = socket; - } - - @Override - public void init() throws IOException { - super.init(); - - sslSocket.startHandshake(); - } -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/net/Socks4ProxySocketFactory.java b/jodd-http/src/main/java/jodd/http/net/Socks4ProxySocketFactory.java deleted file mode 100644 index 4cd167477..000000000 --- a/jodd-http/src/main/java/jodd/http/net/Socks4ProxySocketFactory.java +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.HttpException; -import jodd.http.ProxyInfo; -import jodd.http.Sockets; - -import javax.net.SocketFactory; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; - -/** - * Socket factory for SOCKS4 proxy. This proxy does not do password authentication. - * - * See: http://www.openssh.com/txt/socks5.protocol for more details. - */ -public class Socks4ProxySocketFactory extends SocketFactory { - - private final ProxyInfo proxy; - private final int connectionTimeout; - - public Socks4ProxySocketFactory(final ProxyInfo proxy, final int connectionTimeout) { - this.proxy = proxy; - this.connectionTimeout = connectionTimeout; - } - - @Override - public Socket createSocket(final String host, final int port) { - return createSocks4ProxySocket(host, port); - } - - @Override - public Socket createSocket(final String host, final int port, final InetAddress localHost, final int localPort) { - return createSocks4ProxySocket(host, port); - } - - @Override - public Socket createSocket(final InetAddress host, final int port) { - return createSocks4ProxySocket(host.getHostAddress(), port); - } - - @Override - public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddress, final int localPort) { - return createSocks4ProxySocket(address.getHostAddress(), port); - } - - /** - * Connects to the SOCKS4 proxy and returns proxified socket. - */ - private Socket createSocks4ProxySocket(final String host, final int port) { - Socket socket = null; - final String proxyHost = proxy.getProxyAddress(); - final int proxyPort = proxy.getProxyPort(); - final String user = proxy.getProxyUsername(); - - try { - socket = Sockets.connect(proxyHost, proxyPort, connectionTimeout); - - final InputStream in = socket.getInputStream(); - final OutputStream out = socket.getOutputStream(); - - socket.setTcpNoDelay(true); - - byte[] buf = new byte[1024]; - - // 1) CONNECT - - int index = 0; - buf[index++] = 4; - buf[index++] = 1; - - buf[index++] = (byte) (port >>> 8); - buf[index++] = (byte) (port & 0xff); - - InetAddress addr = InetAddress.getByName(host); - byte[] byteAddress = addr.getAddress(); - for (byte byteAddres : byteAddress) { - buf[index++] = byteAddres; - } - - if (user != null) { - System.arraycopy(user.getBytes(), 0, buf, index, user.length()); - index += user.length(); - } - buf[index++] = 0; - out.write(buf, 0, index); - - // 2) RESPONSE - - int len = 6; - int s = 0; - while (s < len) { - int i = in.read(buf, s, len - s); - if (i <= 0) { - throw new HttpException(ProxyInfo.ProxyType.SOCKS4, "stream is closed"); - } - s += i; - } - if (buf[0] != 0) { - throw new HttpException(ProxyInfo.ProxyType.SOCKS4, "proxy returned VN " + buf[0]); - } - if (buf[1] != 90) { - try { - socket.close(); - } catch (Exception ignore) { - } - throw new HttpException(ProxyInfo.ProxyType.SOCKS4, "proxy returned CD " + buf[1]); - } - - byte[] temp = new byte[2]; - in.read(temp, 0, 2); - - return socket; - } catch (RuntimeException rtex) { - closeSocket(socket); - throw rtex; - } catch (Exception ex) { - closeSocket(socket); - throw new HttpException(ProxyInfo.ProxyType.SOCKS4, ex.toString(), ex); - } - } - - /** - * Closes socket silently. - */ - private void closeSocket(final Socket socket) { - try { - if (socket != null) { - socket.close(); - } - } catch (Exception ignore) { - } - } -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/net/Socks5ProxySocketFactory.java b/jodd-http/src/main/java/jodd/http/net/Socks5ProxySocketFactory.java deleted file mode 100644 index eeb05771e..000000000 --- a/jodd-http/src/main/java/jodd/http/net/Socks5ProxySocketFactory.java +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.net; - -import jodd.http.HttpException; -import jodd.http.ProxyInfo; -import jodd.http.Sockets; - -import javax.net.SocketFactory; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; - -/** - * Socket factory for SOCKS5 proxy. - * - * See: http://www.ietf.org/rfc/rfc1928.txt - */ -public class Socks5ProxySocketFactory extends SocketFactory { - - private final ProxyInfo proxy; - private final int connectionTimeout; - - public Socks5ProxySocketFactory(final ProxyInfo proxy, final int connectionTimeout) { - this.proxy = proxy; - this.connectionTimeout = connectionTimeout; - } - - @Override - public Socket createSocket(final String host, final int port) { - return createSocks5ProxySocket(host, port); - } - - @Override - public Socket createSocket(final String host, final int port, final InetAddress localHost, final int localPort) { - return createSocks5ProxySocket(host, port); - } - - @Override - public Socket createSocket(final InetAddress host, final int port) { - return createSocks5ProxySocket(host.getHostAddress(), port); - } - - @Override - public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddress, final int localPort) { - return createSocks5ProxySocket(address.getHostAddress(), port); - } - - private Socket createSocks5ProxySocket(final String host, final int port) { - Socket socket = null; - - String proxyAddress = proxy.getProxyAddress(); - int proxyPort = proxy.getProxyPort(); - String user = proxy.getProxyUsername(); - String passwd = proxy.getProxyPassword(); - - try { - socket = Sockets.connect(proxyAddress, proxyPort, connectionTimeout); - - final InputStream in = socket.getInputStream(); - final OutputStream out = socket.getOutputStream(); - - socket.setTcpNoDelay(true); - - byte[] buf = new byte[1024]; - int index = 0; - - // 1) VERSION IDENT/METHOD SELECTION - - buf[index++] = 5; - - buf[index++] = 2; - buf[index++] = 0; // NO AUTHENTICATION REQUIRED - buf[index++] = 2; // USERNAME/PASSWORD - - out.write(buf, 0, index); - - // 2) RESPONSE - // in.read(buf, 0, 2); - readBytes(in, buf, 2); - - boolean check = false; - switch ((buf[1]) & 0xff) { - case 0: // NO AUTHENTICATION REQUIRED - check = true; - break; - case 2: // USERNAME/PASSWORD - if (user == null || passwd == null) { - break; - } - - // 3) USER/PASS REQUEST - - index = 0; - buf[index++] = 1; - buf[index++] = (byte) (user.length()); - System.arraycopy(user.getBytes(), 0, buf, index, user.length()); - - index += user.length(); - buf[index++] = (byte) (passwd.length()); - System.arraycopy(passwd.getBytes(), 0, buf, index, passwd.length()); - index += passwd.length(); - - out.write(buf, 0, index); - - // 4) RESPONSE, VERIFIED - // in.read(buf, 0, 2); - readBytes(in, buf, 2); - if (buf[1] == 0) { - check = true; - } - break; - default: - } - - if (!check) { - try { - socket.close(); - } catch (Exception ignore) { - } - throw new HttpException(ProxyInfo.ProxyType.SOCKS5, "check failed"); - } - - // 5) CONNECT - - index = 0; - buf[index++] = 5; - buf[index++] = 1; // CONNECT - buf[index++] = 0; - - byte[] hostb = host.getBytes(); - int len = hostb.length; - buf[index++] = 3; // DOMAINNAME - buf[index++] = (byte) (len); - System.arraycopy(hostb, 0, buf, index, len); - - index += len; - buf[index++] = (byte) (port >>> 8); - buf[index++] = (byte) (port & 0xff); - - out.write(buf, 0, index); - - // 6) RESPONSE - - // in.read(buf, 0, 4); - readBytes(in, buf, 4); - - if (buf[1] != 0) { - try { - socket.close(); - } catch (Exception ignore) { - } - throw new HttpException(ProxyInfo.ProxyType.SOCKS5, "proxy returned " + buf[1]); - } - - switch (buf[3] & 0xff) { - case 1: - // in.read(buf, 0, 6); - readBytes(in, buf, 6); - break; - case 3: - // in.read(buf, 0, 1); - readBytes(in, buf, 1); - // in.read(buf, 0, buf[0]+2); - readBytes(in, buf, (buf[0] & 0xff) + 2); - break; - case 4: - // in.read(buf, 0, 18); - readBytes(in, buf, 18); - break; - default: - } - return socket; - } catch (RuntimeException rttex) { - closeSocket(socket); - throw rttex; - } catch (Exception ex) { - closeSocket(socket); - throw new HttpException(ProxyInfo.ProxyType.SOCKS5, ex.toString(), ex); - } - } - - private void readBytes(final InputStream in, final byte[] buf, final int len) throws IOException { - int s = 0; - while (s < len) { - int i = in.read(buf, s, len - s); - if (i <= 0) { - throw new HttpException(ProxyInfo.ProxyType.SOCKS5, "stream is closed"); - } - s += i; - } - } - - /** - * Closes socket silently. - */ - private void closeSocket(final Socket socket) { - try { - if (socket != null) { - socket.close(); - } - } catch (Exception ignore) { - } - } - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/net/TrustManagers.java b/jodd-http/src/main/java/jodd/http/net/TrustManagers.java deleted file mode 100644 index f2ef34ded..000000000 --- a/jodd-http/src/main/java/jodd/http/net/TrustManagers.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -package jodd.http.net; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509ExtendedTrustManager; -import java.net.Socket; -import java.security.cert.X509Certificate; - -public class TrustManagers { - /** - * Array of trust managers that allow all certificates, done in Java8 proper-way. - */ - public static TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{ - new X509ExtendedTrustManager() { - @Override - public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s) { - } - @Override - public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s) { - } - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - @Override - public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s, final Socket socket) { - } - @Override - public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s, final Socket socket) { - } - @Override - public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s, final SSLEngine sslEngine) { - } - @Override - public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s, final SSLEngine sslEngine) { - } - } - }; -} diff --git a/jodd-http/src/main/java/jodd/http/net/package-info.java b/jodd-http/src/main/java/jodd/http/net/package-info.java deleted file mode 100644 index 0e3651940..000000000 --- a/jodd-http/src/main/java/jodd/http/net/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Implementations and core HTTP stuff. - */ -package jodd.http.net; \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/package-info.java b/jodd-http/src/main/java/jodd/http/package-info.java deleted file mode 100644 index 39fb97dc3..000000000 --- a/jodd-http/src/main/java/jodd/http/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Tiny, raw and simple socket-based HTTP client. - */ -package jodd.http; \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/up/ByteArrayUploadable.java b/jodd-http/src/main/java/jodd/http/up/ByteArrayUploadable.java deleted file mode 100644 index 8d46ce44c..000000000 --- a/jodd-http/src/main/java/jodd/http/up/ByteArrayUploadable.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.up; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * Uploadable wrapper of byte array. - */ -public class ByteArrayUploadable implements Uploadable { - - protected final byte[] byteArray; - protected final String fileName; - protected final String mimeType; - - public ByteArrayUploadable(final byte[] byteArray, final String fileName) { - this.byteArray = byteArray; - this.fileName = fileName; - this.mimeType = null; - } - - public ByteArrayUploadable(final byte[] byteArray, final String fileName, final String mimeType) { - this.byteArray = byteArray; - this.fileName = fileName; - this.mimeType = mimeType; - } - - @Override - public byte[] getContent() { - return byteArray; - } - - @Override - public byte[] getBytes() { - return byteArray; - } - - @Override - public String getFileName() { - return fileName; - } - - @Override - public String getMimeType() { - return mimeType; - } - - @Override - public int getSize() { - return byteArray.length; - } - - @Override - public InputStream openInputStream() { - return new ByteArrayInputStream(byteArray); - } -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/up/FileUploadable.java b/jodd-http/src/main/java/jodd/http/up/FileUploadable.java deleted file mode 100644 index d249c3f6e..000000000 --- a/jodd-http/src/main/java/jodd/http/up/FileUploadable.java +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.up; - -import jodd.http.HttpException; -import jodd.io.FileNameUtil; -import jodd.io.FileUtil; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * File uploadable. - */ -public class FileUploadable implements Uploadable { - - protected final File file; - protected final String fileName; - protected final String mimeType; - - public FileUploadable(final File file) { - this.file = file; - this.fileName = FileNameUtil.getName(file.getName()); - this.mimeType = null; - } - - public FileUploadable(final File file, final String fileName, final String mimeType) { - this.file = file; - this.fileName = fileName; - this.mimeType = mimeType; - } - - @Override - public File getContent() { - return file; - } - - @Override - public byte[] getBytes() { - try { - return FileUtil.readBytes(file); - } catch (IOException ioex) { - throw new HttpException(ioex); - } - } - - @Override - public String getFileName() { - return fileName; - } - - @Override - public String getMimeType() { - return mimeType; - } - - @Override - public int getSize() { - return (int) file.length(); - } - - @Override - public InputStream openInputStream() throws IOException { - return new FileInputStream(file); - } -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/up/Uploadable.java b/jodd-http/src/main/java/jodd/http/up/Uploadable.java deleted file mode 100644 index 62b4613a1..000000000 --- a/jodd-http/src/main/java/jodd/http/up/Uploadable.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.up; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Common interface of uploaded content for {@link jodd.http.HttpBase#form() form parameters}. - * All supported objects that can be uploaded using - * the {@link jodd.http.HttpBase#form(String, Object)} has to - * be wrapped with this interface. - */ -public interface Uploadable { - - /** - * Returns the original content. - */ - public T getContent(); - - /** - * Returns content bytes. - */ - public byte[] getBytes(); - - /** - * Returns content file name. - * If null, the field's name will be used. - */ - public String getFileName(); - - /** - * Returns MIME type. If null, - * MIME type will be determined from - * {@link #getFileName() file name's} extension. - */ - public String getMimeType(); - - /** - * Returns size in bytes. - */ - public int getSize(); - - /** - * Opens InputStream. User is responsible - * for closing it. - */ - public InputStream openInputStream() throws IOException; - -} \ No newline at end of file diff --git a/jodd-http/src/main/java/jodd/http/up/package-info.java b/jodd-http/src/main/java/jodd/http/up/package-info.java deleted file mode 100644 index 659d7726e..000000000 --- a/jodd-http/src/main/java/jodd/http/up/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Uploadable content and few implementations. - */ -package jodd.http.up; \ No newline at end of file diff --git a/jodd-http/src/main/resources/META-INF/LICENSE b/jodd-http/src/main/resources/META-INF/LICENSE deleted file mode 100644 index a040a3b95..000000000 --- a/jodd-http/src/main/resources/META-INF/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2003-present, Jodd Team (https://jodd.org) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/jodd-http/src/test/java/jodd/http/BufferTest.java b/jodd-http/src/test/java/jodd/http/BufferTest.java deleted file mode 100644 index 28c546afa..000000000 --- a/jodd-http/src/test/java/jodd/http/BufferTest.java +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.up.Uploadable; -import jodd.util.StringUtil; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -class BufferTest { - - class SimpleUploadable implements Uploadable { - - final int size; - final byte[] bytes; - - public SimpleUploadable(final int size) { - this.size = size; - bytes = new byte[size]; - - Arrays.fill(bytes, (byte) '*'); - } - public SimpleUploadable(final int size, final char c) { - this.size = size; - bytes = new byte[size]; - - Arrays.fill(bytes, (byte) c); - } - - public Object getContent() { - return bytes; - } - - public byte[] getBytes() { - return bytes; - } - - public String getFileName() { - return null; - } - - public String getMimeType() { - return null; - } - - public int getSize() { - return size; - } - - public InputStream openInputStream() throws IOException { - return new ByteArrayInputStream(bytes); - } - } - - class SimpleProgressListener extends HttpProgressListener { - - StringBuilder sb = new StringBuilder(); - - @Override - public int callbackSize(final int size) { - return 10; - } - - @Override - public void transferred(final int len) { - if (sb.length() > 0) { - sb.append(':'); - } - sb.append(len); - } - } - - @Test - void testBufferAppend() { - final Buffer buffer = new Buffer(); - - assertEquals(0, buffer.size()); - assertEquals(0, buffer.list.size()); - assertNull(buffer.last); - - buffer.append("Hey"); - - assertEquals(3, buffer.size()); - assertNotNull(buffer.last); - - buffer.append('!'); - buffer.append(91); - - assertEquals(6, buffer.size()); - assertEquals(1, buffer.list.size()); - - buffer.append(new SimpleUploadable(100)); - - assertEquals(106, buffer.size()); - assertEquals(2, buffer.list.size()); - assertNull(buffer.last); - - buffer.append("x"); - - assertEquals(107, buffer.size()); - assertEquals(3, buffer.list.size()); - assertNotNull(buffer.last); - - final Buffer buffer2 = new Buffer(); - buffer2.append(new SimpleUploadable(20)); - - buffer.append(buffer2); - - assertEquals(127, buffer.size()); - assertEquals(4, buffer.list.size()); - assertNull(buffer.last); - } - - @Test - void testBufferWrite1() throws IOException { - Buffer buffer; - ByteArrayOutputStream baos; - SimpleProgressListener hpl; - - // size < callbackSize - - buffer = new Buffer(); - buffer.append("12345"); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("12345", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:5", hpl.sb.toString()); - - // size = callbackSize - - buffer = new Buffer(); - buffer.append("1234567890"); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("1234567890", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10", hpl.sb.toString()); - - // size > callbackSize - - buffer = new Buffer(); - buffer.append("1234567890ABC"); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("1234567890ABC", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:13", hpl.sb.toString()); - } - - @Test - void testBufferWrite2() throws IOException { - Buffer buffer; - ByteArrayOutputStream baos; - SimpleProgressListener hpl; - - // size > callbackSize - - buffer = new Buffer(); - buffer.append("12345"); - buffer.append(new SimpleUploadable(10)); - buffer.append("67"); - assertEquals(17, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("12345**********67", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:17", hpl.sb.toString()); - - // size = callbackSize - - buffer = new Buffer(); - buffer.append("12345"); - buffer.append(new SimpleUploadable(5)); - assertEquals(10, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("12345*****", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10", hpl.sb.toString()); - - // size > callbackSize - - buffer = new Buffer(); - buffer.append("12345"); - buffer.append(new SimpleUploadable(21)); - buffer.append("X"); - assertEquals(27, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("12345*********************X", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:20:27", hpl.sb.toString()); - } - - @Test - void testBufferWrite3() throws IOException { - final Buffer buffer; - ByteArrayOutputStream baos; - SimpleProgressListener hpl; - - // - - buffer = new Buffer(); - buffer.append(new SimpleUploadable(4, '*')); - buffer.append(new SimpleUploadable(4, '+')); - buffer.append(new SimpleUploadable(4, '-')); - assertEquals(12, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("****++++----", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:12", hpl.sb.toString()); - - // - - buffer.append("12345678"); - assertEquals(20, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("****++++----12345678", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:20", hpl.sb.toString()); - - // - - buffer.append("A"); - assertEquals(21, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("****++++----12345678A", baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:20:21", hpl.sb.toString()); - - // - - buffer.append(new SimpleUploadable(30, '#')); - assertEquals(51, buffer.size()); - - baos = new ByteArrayOutputStream(); - hpl = new SimpleProgressListener(); - buffer.writeTo(baos, hpl); - - assertEquals("****++++----12345678A" + StringUtil.repeat('#', 30), baos.toString(StandardCharsets.ISO_8859_1.name())); - assertEquals("0:10:20:30:40:50:51", hpl.sb.toString()); - - } - -} diff --git a/jodd-http/src/test/java/jodd/http/CookieTest.java b/jodd-http/src/test/java/jodd/http/CookieTest.java deleted file mode 100644 index e7ceddebd..000000000 --- a/jodd-http/src/test/java/jodd/http/CookieTest.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class CookieTest { - - @Test - void testCookieParsing() { - Cookie cookie = new Cookie("name=value"); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - assertEquals(null, cookie.getExpires()); - - cookie = new Cookie("name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT"); - - assertEquals("name2", cookie.getName()); - assertEquals("value2", cookie.getValue()); - assertEquals("Wed, 09 Jun 2021 10:18:14 GMT", cookie.getExpires()); - - cookie = new Cookie("LSID=DQAAAEaem_vYg; Path=/accounts; Secure; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly"); - - assertEquals("LSID", cookie.getName()); - assertEquals("DQAAAEaem_vYg", cookie.getValue()); - assertEquals("/accounts", cookie.getPath()); - assertTrue(cookie.isSecure()); - assertTrue(cookie.isHttpOnly()); - } - - @Test - void test395() { - Cookie cookie = new Cookie("name=value;"); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - - cookie = new Cookie("name=value; "); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - - cookie = new Cookie("p_skey=UIJeeZgODkPQgiVcwHJBhq9mYrZC9JdpYF6SCZ3fNfY_; PATH=/; DOMAIN=mail.qq.com; ;"); - - assertEquals("p_skey", cookie.getName()); - assertEquals("UIJeeZgODkPQgiVcwHJBhq9mYrZC9JdpYF6SCZ3fNfY_", cookie.getValue()); - } - - @Test - void testSpecialCookieValues() { - Cookie cookie = new Cookie("name=value"); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - - cookie = new Cookie("name=value;"); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - - // duplicated value - - cookie = new Cookie("name=value;a=b;"); - - assertEquals("name", cookie.getName()); - assertEquals("value", cookie.getValue()); - - // empty value - - cookie = new Cookie("name="); - - assertEquals("name", cookie.getName()); - assertEquals("", cookie.getValue()); - - // empty name - - cookie = new Cookie("=value"); - - assertEquals(null, cookie.getName()); - assertEquals(null, cookie.getValue()); - } - - @Test - void testSetGetCookieFromRequest() { - final HttpRequest request = new HttpRequest(); - request.cookies(new Cookie("one", "two"), new Cookie("one2", "two2")); - - final Cookie[] cookies = request.cookies(); - assertEquals(2, cookies.length); - - assertEquals("one", cookies[0].getName()); - assertEquals("two", cookies[0].getValue()); - - assertEquals("one2", cookies[1].getName()); - assertEquals("two2", cookies[1].getValue()); - - } -} diff --git a/jodd-http/src/test/java/jodd/http/EchoTestServer.java b/jodd-http/src/test/java/jodd/http/EchoTestServer.java deleted file mode 100644 index 83895e437..000000000 --- a/jodd-http/src/test/java/jodd/http/EchoTestServer.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import java.io.File; -import java.io.IOException; -import java.util.Properties; - -public class EchoTestServer extends NanoHTTPD { - - public EchoTestServer() throws IOException { - super(8081, new File(".")); - } - - public String uri; - - public String method; - - public Properties header; - - public Properties params; - - public Properties files; - - public Response serve(String uri, String method, Properties header, Properties parms, Properties files) { - String msg = method + " " + uri; - - this.uri = uri; - this.method = method; - this.header = header; - this.params = parms; - this.files = files; - - return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, msg); - } -} \ No newline at end of file diff --git a/jodd-http/src/test/java/jodd/http/EncodingTest.java b/jodd-http/src/test/java/jodd/http/EncodingTest.java deleted file mode 100644 index 2b1271a9b..000000000 --- a/jodd-http/src/test/java/jodd/http/EncodingTest.java +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.fixture.Data; -import jodd.http.up.ByteArrayUploadable; -import jodd.net.MimeTypes; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class EncodingTest { - - static TestServer testServer; - - @BeforeAll - static void startServer() throws Exception { - testServer = new TomcatServer(); - testServer.start(); - } - - @AfterAll - static void stopServer() throws Exception { - testServer.stop(); - } - - @Test - void testContentTypeHeader() { - final HttpRequest req = HttpRequest.get("localhost/hello"); - - assertNull(req.contentType()); - - req.contentType("text/plain;charset=UTF-8"); - - assertEquals("text/plain", req.mediaType()); - assertEquals("UTF-8", req.charset()); - assertEquals("text/plain;charset=UTF-8", req.contentType()); - - req.mediaType("text/html"); - assertEquals("text/html;charset=UTF-8", req.contentType()); - req.mediaType(null); - assertEquals("text/html;charset=UTF-8", req.contentType()); - req.charset("ASCII"); - assertEquals("text/html;charset=ASCII", req.contentType()); - req.charset(null); - assertEquals("text/html", req.contentType()); - - req.contentType("text/plain;charset=UTF-8;boundary=123"); - assertEquals("text/plain", req.mediaType()); - assertEquals("UTF-8", req.charset()); - assertEquals("text/plain;charset=UTF-8;boundary=123", req.contentType()); - } - - @Test - void testRequestEncoding1() throws IOException { - testRequestEncoding(1); - } - @Test - void testRequestEncoding2() throws IOException { - testRequestEncoding(2); - } - @Test - void testRequestEncoding3() throws IOException { - testRequestEncoding(3); - } - @Test - void testRequestEncoding4() throws IOException { - testRequestEncoding(4); - } - private void testRequestEncoding(final int i) throws IOException { - final HttpRequest request = - (i == 1 || i == 2) ? - HttpRequest.get("http://localhost:8173/echo?id=12"): - HttpRequest.post("http://localhost:8173/echo?id=12"); - - final String utf8String = (i == 1 || i == 3) ? "Hello!" : "хелло!"; - final byte[] utf8Bytes = utf8String.getBytes(StandardCharsets.UTF_8); - final int utf8StringRealLen = utf8Bytes.length; - - request.bodyText(utf8String); - - final String rawBody = request.body(); - assertEquals(utf8StringRealLen, rawBody.length()); - assertArrayEquals(utf8Bytes, request.bodyBytes()); - - final HttpResponse response = request.send(); - assertEquals(200, response.statusCode()); - - // servlet - - if (i < 3) { - assertTrue(Data.ref.get); - assertFalse(Data.ref.post); - } else { - assertFalse(Data.ref.get); - assertTrue(Data.ref.post); - } - - assertEquals(String.valueOf(utf8StringRealLen), Data.ref.header.get("content-length")); - assertEquals("text/html;charset=UTF-8", Data.ref.header.get("content-type")); - assertEquals(utf8String, Data.ref.body); - - // response - - assertEquals(String.valueOf(utf8StringRealLen), response.contentLength()); - assertEquals("text/html;charset=UTF-8", response.contentType()); - assertEquals(utf8String, response.bodyText()); - assertEquals(new String(utf8Bytes, StandardCharsets.ISO_8859_1), response.body()); - } - - @Test - void testFormParams1() { - testFormParams(1); - } - @Test - void testFormParams2() { - testFormParams(2); - } - @Test - void testFormParams3() { - testFormParams(3); - } - private void testFormParams(final int i) { - final String encoding = i == 1 ? "UTF-8" : "CP1251"; - - final HttpRequest request = HttpRequest.post("http://localhost:8173/echo3"); - request.formEncoding(encoding); - - if (i == 3) { - request.charset("UTF-8"); - } - - final String value1 = "value"; - final String value2 = "валуе"; - - request.form("one", value1); - request.form("two", value2); - if (i != 3) { - request.form("enc", encoding); - } - - final HttpResponse httpResponse = request.send(); - - assertEquals("application/x-www-form-urlencoded", request.mediaType()); - if (i == 3) { - assertEquals("UTF-8", request.charset()); - assertEquals("CP1251", request.formEncoding); - } else { - assertNull(request.charset()); - } - - assertFalse(Data.ref.get); - assertTrue(Data.ref.post); - - assertEquals(i == 3 ? 2 : 3, Data.ref.params.size()); - assertEquals(value1, Data.ref.params.get("one")); - assertEquals(value2, Data.ref.params.get("two")); - } - - @Test - void testQueryParams1() throws IOException { - testQueryParams(1); - } - - @Test - @Disabled("Ignored until we figure out how to enable org.apache.catalina.STRICT_SERVLET_COMPLIANCE") - void testQueryParams2() throws IOException { - testQueryParams(2); - } - private void testQueryParams(final int i) throws IOException { - final String encoding = i == 1 ? "UTF-8" : "CP1251"; - - final HttpRequest request = HttpRequest.get("http://localhost:8173/echo2"); - request.queryEncoding(encoding); - - final String value1 = "value"; - final String value2 = "валуе"; - - request.query("one", value1); - request.query("two", value2); - request.query("enc", encoding); - - final HttpResponse httpResponse = request.send(); - - assertTrue(Data.ref.get); - assertFalse(Data.ref.post); - - assertEquals(3, Data.ref.params.size()); - assertEquals(value1, Data.ref.params.get("one")); - assertEquals(value2, Data.ref.params.get("two")); - } - - @Test - void testMultipart() { - final HttpRequest request = HttpRequest.post("http://localhost:8173/echo2"); - request - .formEncoding("UTF-8") // optional - .multipart(true); - - final String value1 = "value"; - final String value2 = "валуе"; - - request.form("one", value1); - request.form("two", value2); - - final HttpResponse httpResponse = request.send(); - - assertEquals("multipart/form-data", request.mediaType()); - - assertFalse(Data.ref.get); - assertTrue(Data.ref.post); - - assertEquals(value1, Data.ref.parts.get("one")); - assertEquals(value2, Data.ref.parts.get("two")); - } - - @Test - void testUploadWithUploadable() throws IOException { - final HttpResponse response = HttpRequest - .post("http://localhost:8173/echo2") - .multipart(true) - .form("id", "12") - .form("file", new ByteArrayUploadable( - "upload тест".getBytes(StandardCharsets.UTF_8), "d ст", MimeTypes.MIME_TEXT_PLAIN)) - .send(); - - assertEquals(200, response.statusCode()); - assertEquals("OK", response.statusPhrase()); - - assertEquals("12", Data.ref.params.get("id")); - assertEquals("upload тест", Data.ref.parts.get("file")); - assertEquals("d ст", Data.ref.fileNames.get("file")); - } - - -} diff --git a/jodd-http/src/test/java/jodd/http/GoogleMapsTest.java b/jodd-http/src/test/java/jodd/http/GoogleMapsTest.java deleted file mode 100644 index 4d414824b..000000000 --- a/jodd-http/src/test/java/jodd/http/GoogleMapsTest.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.FileUtil; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URL; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -class GoogleMapsTest { - - @Test - void testNoBody() throws IOException { - /*HttpResponse httpResponse = HttpRequest.get("http://maps.googleapis.com/maps/api/geocode/json") - .query("address", "14621") - .query("sensor", "false") - .send(); - */ - URL data = RawTest.class.getResource("2-response.txt"); - byte[] fileContent = FileUtil.readBytes(data.getFile()); - - HttpResponse httpResponse = HttpResponse.readFrom(new ByteArrayInputStream(fileContent)); - - try { - httpResponse.bodyText(); - } catch (Exception ex) { - fail(ex.toString()); - } - - assertEquals("", httpResponse.bodyText()); - } - - @Test - void testNoContentLength() throws IOException { - URL data = RawTest.class.getResource("3-response.txt"); - byte[] fileContent = FileUtil.readBytes(data.getFile()); - - HttpResponse httpResponse = HttpResponse.readFrom(new ByteArrayInputStream(fileContent)); - - assertEquals("Body!", httpResponse.bodyText()); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpBrowserOfflineTest.java b/jodd-http/src/test/java/jodd/http/HttpBrowserOfflineTest.java deleted file mode 100644 index af532d76c..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpBrowserOfflineTest.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class HttpBrowserOfflineTest { - - @Test - void testDefaultParameters() { - HttpBrowser httpBrowser = new HttpBrowser(); - httpBrowser.setDefaultHeader("aaa", "123"); - - HttpRequest request = HttpRequest.get("foo.com"); - request.header("bbb", "987"); - - httpBrowser.addDefaultHeaders(request); - - assertEquals(3, request.headerNames().size()); - assertEquals("123", request.header("aaa")); - assertEquals("987", request.header("bbb")); - } - - @Test - void testDefaultParametersOverwrite() { - HttpBrowser httpBrowser = new HttpBrowser(); - httpBrowser.setDefaultHeader("aaa", "123"); - - HttpRequest request = HttpRequest.get("foo.com"); - request.header("aaa", "987"); - - httpBrowser.addDefaultHeaders(request); - - assertEquals(2, request.headerNames().size()); - assertEquals("987", request.header("aaa")); - } -} diff --git a/jodd-http/src/test/java/jodd/http/HttpBrowserTest.java b/jodd-http/src/test/java/jodd/http/HttpBrowserTest.java deleted file mode 100644 index 7a541cb6c..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpBrowserTest.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -class HttpBrowserTest { - - static TestServer testServer; - - @BeforeAll - static void startServer() throws Exception { - testServer = new TomcatServer(); - testServer.start(); - } - - @AfterAll - static void stopServer() throws Exception { - testServer.stop(); - } - - @Test - void testBrowser() { - HttpBrowser httpBrowser = new HttpBrowser(); - - httpBrowser.sendRequest( - HttpRequest - .get("localhost:8173/echo?id=17") - .cookies(new Cookie("waffle", "jam")) - .bodyText("hello")); - - HttpResponse httpResponse = httpBrowser.getHttpResponse(); - - assertNotNull(httpResponse); - assertEquals("hello", httpResponse.body()); - - Cookie[] cookies = httpResponse.cookies(); - assertEquals(1, cookies.length); - - assertEquals("waffle", cookies[0].getName()); - assertEquals("jam!", cookies[0].getValue()); - } - - @Test - void testBrowserRedirect() { - HttpBrowser httpBrowser = new HttpBrowser(); - - httpBrowser.sendRequest(HttpRequest.get("localhost:8173/redirect")); - - HttpResponse httpResponse = httpBrowser.getHttpResponse(); - - assertEquals(200, httpResponse.statusCode()); - assertEquals("target!", httpResponse.body()); - - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpConnectionTest.java b/jodd-http/src/test/java/jodd/http/HttpConnectionTest.java deleted file mode 100644 index 9c164ce0f..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpConnectionTest.java +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.up.ByteArrayUploadable; -import jodd.io.FileUtil; -import jodd.net.MimeTypes; -import jodd.util.StringUtil; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -class HttpConnectionTest { - - @Test - void testEcho() throws IOException { - final EchoTestServer echoTestServer = new EchoTestServer(); - - final HttpResponse response = HttpRequest.get("http://localhost:8081/hello?id=12").send(); - - assertEquals(200, response.statusCode()); - assertEquals("OK", response.statusPhrase()); - - assertEquals("GET", echoTestServer.method); - assertEquals("/hello", echoTestServer.uri); - assertEquals(1, echoTestServer.params.size()); - assertEquals("12", echoTestServer.params.get("id")); - - assertEquals("GET /hello", response.body()); - - echoTestServer.stop(); - } - - @Test - void testUpload() throws IOException { - final EchoTestServer echoTestServer = new EchoTestServer(); - - final File file = FileUtil.createTempFile(); - file.deleteOnExit(); - - FileUtil.writeString(file, "upload тест"); - assertEquals("upload тест", FileUtil.readString(file)); - - final HttpResponse response = HttpRequest - .post("http://localhost:8081/hello") - .form("id", "12") - .form("file", file) - .send(); - - assertEquals(200, response.statusCode()); - assertEquals("OK", response.statusPhrase()); - - assertEquals("POST", echoTestServer.method); - assertEquals("12", echoTestServer.params.get("id")); - final File uploadedFile = new File(echoTestServer.files.get("file").toString()); - assertNotNull(uploadedFile); - assertEquals("upload тест", FileUtil.readString(uploadedFile)); - - assertEquals("POST /hello", response.body()); - - echoTestServer.stop(); - file.delete(); - } - - @Test - void testUploadWithUploadable() throws IOException { - final EchoTestServer echoTestServer = new EchoTestServer(); - - final HttpResponse response = HttpRequest - .post("http://localhost:8081/hello") - .multipart(true) - .form("id", "12") - .form("file", new ByteArrayUploadable( - "upload тест".getBytes(StandardCharsets.UTF_8), "d ст", MimeTypes.MIME_TEXT_PLAIN)) - .send(); - - assertEquals(200, response.statusCode()); - assertEquals("OK", response.statusPhrase()); - - assertEquals("POST", echoTestServer.method); - assertEquals("12", echoTestServer.params.get("id")); - final File uploadedFile = new File(echoTestServer.files.get("file").toString()); - assertNotNull(uploadedFile); - assertEquals("upload тест", FileUtil.readString(uploadedFile)); - - assertEquals("POST /hello", response.body()); - - echoTestServer.stop(); - } - - @Test - void testUploadWithMonitor() throws IOException { - final EchoTestServer echoTestServer = new EchoTestServer(); - - final File file = FileUtil.createTempFile(); - file.deleteOnExit(); - - FileUtil.writeString(file, StringUtil.repeat('A', 1024)); - - final StringBuilder sb = new StringBuilder(); - - final HttpResponse response = HttpRequest - .post("http://localhost:8081/hello") - .form("id", "12") - .form("file", file) - .monitor(new HttpProgressListener() { - @Override - public void transferred(final int len) { - sb.append(":" + len); - } - }) - .send(); - - assertEquals(200, response.statusCode()); - assertEquals("OK", response.statusPhrase()); - - echoTestServer.stop(); - file.delete(); - - assertEquals(":0:512:1024:148", StringUtil.substring(sb.toString(), 0, -1)); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpHeaderTest.java b/jodd-http/src/test/java/jodd/http/HttpHeaderTest.java deleted file mode 100644 index 2870949d7..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpHeaderTest.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class HttpHeaderTest { - - @Test - void testSettingHostsHeader_changeWithSet() { - final HttpRequest httpRequest = HttpRequest.post("jodd.site"); - - assertEquals("jodd.site", httpRequest.host()); - // calling toString as it will set the HEADER - assertTrue(httpRequest.toString().contains("jodd.site")); - - assertEquals("jodd.site", httpRequest.header(HttpRequest.HEADER_HOST)); - - // change - httpRequest.set("oblac.rs"); - - // is the header changed? first check the header - assertEquals("oblac.rs", httpRequest.host()); - assertEquals("oblac.rs", httpRequest.header(HttpRequest.HEADER_HOST)); - // the regenerated request should work - assertTrue(httpRequest.toString().contains("oblac.rs")); - // after the generation - assertEquals("oblac.rs", httpRequest.header(HttpRequest.HEADER_HOST)); - } - - @Test - void testSettingHostsHeader_changeWithHost() { - final HttpRequest httpRequest = HttpRequest.post("jodd.site"); - - assertEquals("jodd.site", httpRequest.host()); - // calling toString as it will set the HEADER - assertTrue(httpRequest.toString().contains("jodd.site")); - - assertEquals("jodd.site", httpRequest.header(HttpRequest.HEADER_HOST)); - - // change - httpRequest.host("oblac.rs"); - - // is the header changed? first check the header - assertEquals("oblac.rs", httpRequest.host()); - assertEquals("oblac.rs", httpRequest.header(HttpRequest.HEADER_HOST)); - // the regenerated request should work - assertTrue(httpRequest.toString().contains("oblac.rs")); - // after the generation - assertEquals("oblac.rs", httpRequest.header(HttpRequest.HEADER_HOST)); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpMultiMapTest.java b/jodd-http/src/test/java/jodd/http/HttpMultiMapTest.java deleted file mode 100644 index 7ba2d1993..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpMultiMapTest.java +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -class HttpMultiMapTest { - - @Test - void testAdd() { - HttpMultiMap mm = HttpMultiMap.newCaseInsensitiveMap(); - - mm.add("One", "one"); - mm.add("Two", "two"); - - assertEquals(2, mm.size()); - assertEquals("one", mm.get("one")); - assertEquals("two", mm.get("two")); - } - - @Test - void testAddSameName() { - HttpMultiMap mm = HttpMultiMap.newCaseInsensitiveMap(); - - mm.add("One", "one"); - mm.add("one", "two"); - - assertEquals(1, mm.size()); - assertEquals("two", mm.get("one")); - - List all = mm.getAll("one"); - assertEquals(2, all.size()); - assertEquals("one", all.get(0)); - assertEquals("two", all.get(1)); - - mm.add("one", "three"); - all = mm.getAll("one"); - assertEquals(3, all.size()); - assertEquals("one", all.get(0)); - assertEquals("two", all.get(1)); - assertEquals("three", all.get(2)); - } - - @Test - void testMissing() { - HttpMultiMap mm = HttpMultiMap.newCaseInsensitiveMap(); - - assertNull(mm.get("xxx")); - } - - @Test - void testIterator() { - HttpMultiMap mm = HttpMultiMap.newCaseInsensitiveMap(); - - mm.add("One", "one"); - mm.add("one", "two"); - mm.add("two", "2."); - mm.add("one", "three"); - - assertEquals(2, mm.size()); - - Iterator> i = mm.iterator(); - - assertTrue(i.hasNext()); - assertEquals("one", i.next().getValue()); - assertEquals("two", i.next().getValue()); - assertEquals("2.", i.next().getValue()); - assertEquals("three", i.next().getValue()); - assertFalse(i.hasNext()); - - try { - i.next(); - fail("error"); - } catch (Exception ignore) {} - - mm.clear(); - i = mm.iterator(); - assertFalse(i.hasNext()); - } - - - @Test - void testNullValues() { - HttpMultiMap hmm = HttpMultiMap.newCaseInsensitiveMap(); - - assertFalse(hmm.contains("one")); - - hmm.add("one", null); - - assertNull(hmm.get("one")); - assertTrue(hmm.contains("one")); - - hmm.add("one", null); - - assertNull(hmm.get("one")); - assertTrue(hmm.contains("one")); - - hmm.set("one", "1"); - - assertEquals("1", hmm.get("one")); - assertTrue(hmm.contains("one")); - } - - @Test - void testParametersNumber() { - HttpMultiMap hmm = HttpMultiMap.newCaseInsensitiveMap(); - - for (int i = 0; i < 30; i++) { - hmm.add(String.valueOf(i), "!" + i); - } - - assertEquals(30, hmm.size()); - } - - @Test - void testLetterCaseInsensitive() { - HttpMultiMap mm = HttpMultiMap.newCaseInsensitiveMap(); - - mm.add("one", "1.1"); - mm.add("one", "1.1.1"); - mm.add("One", "1.2"); - - assertEquals(1, mm.size()); - - assertEquals("1.2", mm.get("one")); - assertEquals("1.2", mm.get("ONE")); - assertEquals("1.2", mm.get("One")); - - List list = mm.getAll("ONE"); - - assertEquals(3, list.size()); - - assertEquals(1, mm.names().size()); - assertEquals(3, mm.entries().size()); - } - - @Test - void testLetterCaseSensitive() { - HttpMultiMap mm = HttpMultiMap.newCaseSensitiveMap(); - - mm.add("one", "1.1"); - mm.add("one", "1.1.1"); - mm.add("One", "1.2"); - - assertEquals(2, mm.size()); - - assertEquals("1.1.1", mm.get("one")); - assertNull(mm.get("ONE")); - assertEquals("1.2", mm.get("One")); - - List list = mm.getAll("ONE"); - assertEquals(0, list.size()); - - list = mm.getAll("one"); - assertEquals(2, list.size()); - - assertEquals(2, mm.names().size()); - assertEquals(3, mm.entries().size()); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpProgressListenerTest.java b/jodd-http/src/test/java/jodd/http/HttpProgressListenerTest.java deleted file mode 100644 index f0359dda2..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpProgressListenerTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class HttpProgressListenerTest { - - @Test - void testHttpProgressListener() { - HttpProgressListener hpl = new HttpProgressListener() { - @Override - public void transferred(int len) { - - } - }; - - assertEquals(512, hpl.callbackSize(0)); - assertEquals(512, hpl.callbackSize(1000)); - assertEquals(512, hpl.callbackSize(51200)); - assertEquals(512, hpl.callbackSize(51201)); - - assertEquals(1024, hpl.callbackSize(102400)); - assertEquals(1024, hpl.callbackSize(102401)); - assertEquals(1024, hpl.callbackSize(102449)); - - assertEquals(1025, hpl.callbackSize(102450)); - assertEquals(1025, hpl.callbackSize(102499)); - assertEquals(1025, hpl.callbackSize(102500)); - } -} diff --git a/jodd-http/src/test/java/jodd/http/HttpRedirectTest.java b/jodd-http/src/test/java/jodd/http/HttpRedirectTest.java deleted file mode 100644 index 0a2f66286..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpRedirectTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -class HttpRedirectTest { - - static TestServer testServer; - - @BeforeAll - static void startServer() throws Exception { - testServer = new TomcatServer(); - testServer.start(); - } - - @AfterAll - static void stopServer() throws Exception { - testServer.stop(); - } - - @Test - void testRedirect() { - HttpRequest httpRequest = HttpRequest.get("localhost:8173/redirect"); - - HttpResponse httpResponse = httpRequest.send(); - - assertEquals(302, httpResponse.statusCode); - - HttpBrowser httpBrowser = new HttpBrowser(); - - httpBrowser.sendRequest( - HttpRequest.get("localhost:8173/redirect")); - - httpResponse = httpBrowser.getHttpResponse(); - - assertNotNull(httpResponse); - assertEquals("target!", httpResponse.body()); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpRequestTest.java b/jodd-http/src/test/java/jodd/http/HttpRequestTest.java deleted file mode 100644 index bff548683..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpRequestTest.java +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.FileUtil; -import jodd.io.IOUtil; -import jodd.io.upload.FileUpload; -import jodd.net.MimeTypes; -import jodd.util.ResourcesUtil; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.CharArrayWriter; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -class HttpRequestTest { - - @Test - void testQueryParameters() { - final HttpRequest httpRequest = new HttpRequest(); - - httpRequest.path(""); - assertEquals("/", httpRequest.path()); - - httpRequest.path("jodd"); - assertEquals("/jodd", httpRequest.path()); - assertNotNull(httpRequest.query()); - assertEquals(0, httpRequest.query().size()); - - httpRequest.queryString("one=two"); - assertEquals("/jodd", httpRequest.path()); - - HttpMultiMap params = httpRequest.query(); - assertEquals(1, params.size()); - assertEquals("two", params.get("one")); - - httpRequest.queryString("one"); - assertEquals("one", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(1, params.size()); - assertNull(params.get("one")); - - httpRequest.queryString("one="); - assertEquals("one=", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(1, params.size()); - assertEquals("", params.get("one")); - - httpRequest.queryString("one=aaa&two=bbb"); - assertEquals("one=aaa&two=bbb", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(2, params.size()); - assertEquals("aaa", params.get("one")); - assertEquals("bbb", params.get("two")); - - httpRequest.queryString("one=&two=aaa"); - assertEquals("one=&two=aaa", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(2, params.size()); - assertEquals("", params.get("one")); - assertEquals("aaa", params.get("two")); - - httpRequest.clearQueries(); - httpRequest.queryString("one=Супер"); - assertEquals("one=%D0%A1%D1%83%D0%BF%D0%B5%D1%80", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(1, params.size()); - assertEquals("Супер", params.get("one")); - - httpRequest.queryString("one=Sуp"); - assertEquals("one=S%D1%83p", httpRequest.queryString()); - - httpRequest.queryString("one=1&one=2"); - assertEquals("one=1&one=2", httpRequest.queryString()); - params = httpRequest.query(); - assertEquals(1, params.size()); - assertEquals("1", params.getAll("one").get(0)); - assertEquals("2", params.getAll("one").get(1)); - - httpRequest.query("one", Integer.valueOf(3)); - assertEquals("one=1&one=2&one=3", httpRequest.queryString()); - } - - @Test - void testFormParamsObjects() { - final Map params = new HashMap<>(); - params.put("state", 1); - - final HttpRequest httpRequest = new HttpRequest(); - httpRequest.form(params); - - assertEquals(1, httpRequest.form().size()); - } - - @Test - void testSet() { - HttpRequest httpRequest = new HttpRequest(); - httpRequest.set("GET http://jodd.org:173/index.html?light=true"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("jodd.org", httpRequest.host()); - assertEquals(173, httpRequest.port()); - assertEquals("/index.html", httpRequest.path()); - assertEquals("true", httpRequest.query().get("light")); - - - httpRequest = new HttpRequest(); - httpRequest.set("http://jodd.org:173/index.html?light=true"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("jodd.org", httpRequest.host()); - assertEquals(173, httpRequest.port()); - assertEquals("/index.html", httpRequest.path()); - assertEquals("true", httpRequest.query().get("light")); - - - httpRequest = new HttpRequest(); - httpRequest.set("jodd.org:173/index.html?light=true"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("jodd.org", httpRequest.host()); - assertEquals(173, httpRequest.port()); - assertEquals("/index.html", httpRequest.path()); - assertEquals("true", httpRequest.query().get("light")); - - - httpRequest = new HttpRequest(); - httpRequest.set("jodd.org/index.html?light=true"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("jodd.org", httpRequest.host()); - assertEquals(80, httpRequest.port()); - assertEquals("/index.html", httpRequest.path()); - assertEquals("true", httpRequest.query().get("light")); - - - httpRequest = new HttpRequest(); - httpRequest.set("/index.html?light=true"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("localhost", httpRequest.host()); - assertEquals(80, httpRequest.port()); - assertEquals("/index.html", httpRequest.path()); - assertEquals("true", httpRequest.query().get("light")); - - - httpRequest = new HttpRequest(); - httpRequest.set("http://jodd.org"); - - assertEquals("GET", httpRequest.method()); - assertEquals("http", httpRequest.protocol()); - assertEquals("jodd.org", httpRequest.host()); - assertEquals(80, httpRequest.port()); - assertEquals("/", httpRequest.path()); - } - - - @Test - void testInOutForm() { - final HttpRequest request = HttpRequest.get("http://jodd.org/?id=173"); - request.header("User-Agent", "Scaly"); - request.form("one", "funny"); - - final byte[] bytes = request.toByteArray(); - - // read - final HttpRequest request2 = HttpRequest.readFrom(new ByteArrayInputStream(bytes)); - - assertEquals(request.method(), request2.method()); - assertEquals(request.path(), request2.path()); - assertEquals(request.queryString(), request2.queryString()); - - assertEquals(request.header("User-Agent"), request2.header("User-Agent")); - assertEquals(request.header("Content-Type"), request2.header("content-type")); - assertEquals(request.header("Content-Length"), request2.header("content-length")); - - final HttpMultiMap params1 = request.form(); - final HttpMultiMap params2 = request2.form(); - assertEquals(params1.size(), params2.size()); - assertEquals(params2.get("one"), params2.get("one")); - } - - @Test - void testNegativeContentLength() { - HttpRequest request = HttpRequest.get("http://jodd.org/?id=173"); - request.contentLength(-123); - - byte[] bytes = request.toByteArray(); - try { - final HttpRequest request2 = HttpRequest.readFrom(new ByteArrayInputStream(bytes)); - assertEquals("", request2.body()); - } catch (final Exception ex) { - fail(ex.toString()); - } - - // the same test but with missing content length - - request = HttpRequest.get("http://jodd.org/?id=173"); - - bytes = request.toByteArray(); - try { - final HttpRequest request2 = HttpRequest.readFrom(new ByteArrayInputStream(bytes)); - assertEquals("", request2.body()); - } catch (final Exception ex) { - fail(ex.toString()); - } - } - - @Test - void testFileUpload() throws IOException { - final HttpRequest request = HttpRequest.get("http://jodd.org/?id=173"); - - request.header("User-Agent", "Scaly").form("one", "funny"); - - final File tempFile = FileUtil.createTempFile(); - tempFile.deleteOnExit(); - FileUtil.writeString(tempFile, "qwerty"); - request.form("two", tempFile); - - final byte[] bytes = request.toByteArray(); - - - // read - final HttpRequest request2 = HttpRequest.readFrom(new ByteArrayInputStream(bytes)); - final HttpMultiMap httpParams2 = request2.form(); - - assertEquals(request.method(), request2.method()); - assertEquals(request.path(), request2.path()); - assertEquals(request.queryString(), request2.queryString()); - - assertEquals(request.header("User-Agent"), request2.header("User-Agent")); - assertEquals(request.header("Content-Type"), request2.header("content-type")); - assertEquals(request.header("Content-Length"), request2.header("content-length")); - - final HttpMultiMap params1 = request.form(); - final HttpMultiMap params2 = request2.form(); - assertEquals(params1.size(), params2.size()); - assertEquals(params2.get("one"), params2.get("one")); - - final FileUpload fu = (FileUpload) httpParams2.get("two"); - assertEquals(6, fu.getSize()); - - final String str = new String(fu.getFileContent()); - assertEquals("qwerty", str); - - tempFile.delete(); - } - - @Test - void testUrl() { - HttpRequest httpRequest = new HttpRequest(); - httpRequest.set("GET http://jodd.org:173/index.html?light=true"); - - assertEquals("http://jodd.org:173/index.html?light=true", httpRequest.url()); - assertEquals("http://jodd.org:173", httpRequest.hostUrl()); - - httpRequest = HttpRequest.get("foo.com/"); - - assertEquals("http://foo.com", httpRequest.hostUrl()); - } - - @Test - void testBasicAuthorizationCanBeSetToNullAndIsIgnoredSilently() { - final HttpRequest httpRequest = new HttpRequest(); - final String[][] input = new String[][]{ - {"non-null", null}, - {null, "non-null"}, - {null, null}, - }; - - try { - - for(final String[] pair :input) { - httpRequest.basicAuthentication(pair[0], pair[1]); - assertNull(httpRequest.headers.get("Authorization")); - } - - } catch (final RuntimeException e) { - fail("No exception should be thrown for null authorization basic header args!"); - } - } - - @Test - void test394() { - HttpRequest request = HttpRequest.get("https://jodd.org/random link"); - assertEquals("GET", request.method()); - assertEquals("https://jodd.org/random link", request.url()); - - request = HttpRequest.get("https://jodd.org/random link?q=1"); - assertEquals("1", request.query().get("q")); - - final String badUrl = "httpsjodd.org/random link?q=1:// GET"; - try { - HttpRequest.get(badUrl).send(); - fail("error"); - } - catch (final HttpException he) { - assertTrue(he.getMessage().contains(badUrl)); - } - - } - - @Test - void testCapitalizeHeaders() { - - // true - - HttpRequest request = HttpRequest.get("") - .capitalizeHeaderKeys(true) - .header("key-tEST2", "value2"); - assertTrue(request.toString(false).contains("Key-Test2: value2"), "Header key should have been modified"); - assertEquals("value2", request.headers("KEY-TEST2").get(0)); - assertEquals("value2", request.headers("key-test2").get(0)); - - request.header("key-test2", "value3"); - assertTrue(request.toString(false).contains("Key-Test2: value2, value3"), "Header key should have been modified"); - assertEquals(2, request.headers("KEY-TEST2").size()); - assertEquals(2 + 2, request.headerNames().size()); // 2 default and 2 added - - request.headerRemove("key-test2"); - assertFalse(request.headers.contains("key-test2")); - assertFalse(request.headers.contains("key-tEST2")); - - - // false - - request = HttpRequest.get("") - .capitalizeHeaderKeys(false) - .header("KEY-TEST1", "VALUE1"); - - assertTrue(request.toString(false).contains("KEY-TEST1: VALUE1"), "Header key should not have been modified"); - assertEquals("VALUE1", request.headers("KEY-TEST1").get(0)); - assertEquals("VALUE1", request.headers("key-test1").get(0)); - - request.header("key-test1", "value4"); - assertTrue(request.toString(false).contains("key-test1: VALUE1, value4"), "Header key should not have been modified"); - assertEquals(2, request.headers("KEY-TEST1").size()); - assertEquals(2 + 2, request.headerNames().size()); // 2 default and 2 added - - request.headerRemove("key-test1"); - assertFalse(request.headers.contains("key-test1")); - assertFalse(request.headers.contains("KEY-TEST1")); - } - - @Test - void testBigRequest() throws IOException { - final InputStream inputStream = ResourcesUtil.getResourceAsStream("/jodd/http/answer.json"); - - final CharArrayWriter writter = IOUtil.copy(inputStream); - final String body = writter.toString(); - - final HttpRequest httpRequest = HttpRequest.get("").body(body); - - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - httpRequest.sendTo(outputStream); - - String receivedBody = outputStream.toString(); - - final int ndx = receivedBody.indexOf("{"); - receivedBody = receivedBody.substring(ndx); - - assertEquals(body, receivedBody); - } - - @Test - void testHttpRequestSlash() { - final HttpRequest request = HttpRequest.post("/"); - request.contentType("application/x-www-form-urlencoded"); - final HttpRequest request1 = HttpRequest.readFrom(new ByteArrayInputStream(request.toByteArray())); - - assertEquals(request.toString(), request1.toString()); - } - - @Test - void testHttpRequestReRead() { - final HttpRequest request = HttpRequest.post("http://127.0.0.1:8086/test"); - request.form("a", null); - request.form("b", "aaa"); - final HttpRequest request1 = HttpRequest.readFrom(new ByteArrayInputStream(request.toByteArray())); - assertEquals(request.toString(), request1.toString()); - } - - @Test - void testHttpRequestContentSetOrder() { - final HttpRequest request = HttpRequest.get("http://127.0.0.1:8086/test"); - - request.contentTypeJson().bodyText("{}"); - - assertEquals(MimeTypes.MIME_APPLICATION_JSON, request.mediaType()); - } -} diff --git a/jodd-http/src/test/java/jodd/http/HttpUploadTest.java b/jodd-http/src/test/java/jodd/http/HttpUploadTest.java deleted file mode 100644 index 81ac99e9e..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpUploadTest.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.FileUtil; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -class HttpUploadTest { - - static TestServer testServer; - - @BeforeAll - static void startServer() throws Exception { - testServer = new TomcatServer(); - testServer.start(); - } - - @AfterAll - static void stopServer() throws Exception { - testServer.stop(); - } - - - @Test - void uploadTest() throws IOException { - File temp1 = FileUtil.createTempFile(); - FileUtil.writeString(temp1, "Temp1 content"); - File temp2 = FileUtil.createTempFile(); - FileUtil.writeString(temp2, "Temp2 content"); - - temp1.deleteOnExit(); - temp2.deleteOnExit(); - - HttpRequest httpRequest = HttpRequest.post("localhost:8173/echo") - .form( - "title", "test", - "description", "Upload test", - "file1", temp1, - "file2", temp2 - ); - - - HttpResponse httpResponse = httpRequest.send(); - String body = httpResponse.bodyText(); - - assertTrue(body.contains("Temp1 content")); - assertTrue(body.contains("Temp2 content")); - } -} diff --git a/jodd-http/src/test/java/jodd/http/HttpUtilTest.java b/jodd-http/src/test/java/jodd/http/HttpUtilTest.java deleted file mode 100644 index 878621fb5..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpUtilTest.java +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class HttpUtilTest { - - @Test - void testNiceHeaderNames() { - assertEquals("Content-Type", HttpUtil.prepareHeaderParameterName("conTent-tyPe")); - assertEquals("ETag", HttpUtil.prepareHeaderParameterName("etag")); - } - - @Test - void testMediaTypeAndParameters() { - String contentType = "text/html"; - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals(null, HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - - contentType = "text/html;"; // special case, see #588 - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals(null, HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - - contentType = "text/html; charset=ISO-8859-4"; - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals("ISO-8859-4", HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - - - contentType = "text/html;charset=ISO-8859-4"; - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals("ISO-8859-4", HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - - - contentType = "text/html; pre=foo; charset=ISO-8859-4"; - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals("ISO-8859-4", HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - - - contentType = "text/html; pre=foo; charset=ISO-8859-4; post=bar"; - - assertEquals("text/html", HttpUtil.extractMediaType(contentType)); - assertEquals("ISO-8859-4", HttpUtil.extractHeaderParameter(contentType, "charset", ';')); - assertEquals("foo", HttpUtil.extractHeaderParameter(contentType, "pre", ';')); - assertEquals(null, HttpUtil.extractHeaderParameter(contentType, "na", ';')); - } - - @Test - void testDefaultPort() { - HttpRequest request; - - request = HttpRequest.get("jodd.org"); - assertEquals("http", request.protocol()); - assertEquals(80, request.port()); - - request = HttpRequest.get("jodd.org:80"); - assertEquals("http", request.protocol()); - assertEquals(80, request.port()); - - request = HttpRequest.get("jodd.org:801"); - assertEquals("http", request.protocol()); - assertEquals(801, request.port()); - - request = HttpRequest.get("http://jodd.org"); - assertEquals("http", request.protocol()); - assertEquals(80, request.port()); - - request = HttpRequest.get("https://jodd.org"); - assertEquals("https", request.protocol()); - assertEquals(443, request.port()); - - request = HttpRequest.get("https://jodd.org:8443"); - assertEquals("https", request.protocol()); - assertEquals(8443, request.port()); - } - - @Test - void testBuildQuery() { - final HttpMultiMap map = HttpMultiMap.newCaseInsensitiveMap(); - - assertEquals("", HttpUtil.buildQuery(map, StandardCharsets.UTF_8.name())); - - map.add("aaa", "one"); - assertEquals("aaa=one", HttpUtil.buildQuery(map, StandardCharsets.UTF_8.name())); - - map.add("bbb", "two"); - assertEquals("aaa=one&bbb=two", HttpUtil.buildQuery(map, StandardCharsets.UTF_8.name())); - - map.clear().add("ccc", null); - assertEquals("ccc", HttpUtil.buildQuery(map, StandardCharsets.UTF_8.name())); - - map.add("ddd", "four"); - assertEquals("ccc&ddd=four", HttpUtil.buildQuery(map, StandardCharsets.UTF_8.name())); - } - - @Test - void testParseQuery() { - HttpMultiMap map = HttpUtil.parseQuery("a=b", false); - - assertEquals(1, map.size()); - assertEquals("b", map.get("a")); - - - map = HttpUtil.parseQuery("a=b&c=d", false); - - assertEquals(2, map.size()); - assertEquals("b", map.get("a")); - assertEquals("d", map.get("c")); - } - - @Test - void testParseQuery_specialCase() { - HttpMultiMap map = HttpUtil.parseQuery("a&b", false); - - assertEquals(2, map.size()); - assertNull(map.get("a")); - assertNull(map.get("b")); - - - map = HttpUtil.parseQuery("a&c=d", false); - - assertEquals(2, map.size()); - assertNull(map.get("a")); - assertEquals("d", map.get("c")); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/HttpsFactoryTest.java b/jodd-http/src/test/java/jodd/http/HttpsFactoryTest.java deleted file mode 100644 index e3c84fadc..000000000 --- a/jodd-http/src/test/java/jodd/http/HttpsFactoryTest.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.http.net.SSLSocketHttpConnectionProvider; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -class HttpsFactoryTest { - - @Test - void testCustomSSLSocketHttpConnectionProvider() { - final AtomicBoolean atomicBoolean = new AtomicBoolean(); - - final SSLSocketFactory sslSocketFactory = new SSLSocketFactory() { - @Override - public String[] getDefaultCipherSuites() { - return new String[0]; - } - - @Override - public String[] getSupportedCipherSuites() { - return new String[0]; - } - - @Override - public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException { - atomicBoolean.set(true); - return null; - } - - @Override - public Socket createSocket(String s, int i) throws IOException, UnknownHostException { - atomicBoolean.set(true); - return null; - } - - @Override - public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException { - atomicBoolean.set(true); - return null; - } - - @Override - public Socket createSocket(InetAddress inetAddress, int i) throws IOException { - atomicBoolean.set(true); - return null; - } - - @Override - public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException { - atomicBoolean.set(true); - return null; - } - }; - - try { - HttpRequest.get("https://google.com") - .withConnectionProvider(new SSLSocketHttpConnectionProvider(sslSocketFactory)) - .open(); - } - catch (NullPointerException npe) { - } - - assertTrue(atomicBoolean.get()); - } -} diff --git a/jodd-http/src/test/java/jodd/http/KeepAliveTest.java b/jodd-http/src/test/java/jodd/http/KeepAliveTest.java deleted file mode 100644 index cc330e106..000000000 --- a/jodd-http/src/test/java/jodd/http/KeepAliveTest.java +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class KeepAliveTest { - - private static final String[] RESPONSES = new String[] { - "HTTP/1.1 200 OK\r\n" + - "Content-Type: text/html; charset=utf-8\r\n" + - "Content-Length: 13\r\n" + - "Connection: Keep-Alive\r\n" + - "Keep-Alive: timeout=100, max=2\r\n" + - "\r\n" + - "", - - "HTTP/1.1 200 OK\r\n" + - "Content-Type: text/html; charset=utf-8\r\n" + - "Content-Length: 13\r\n" + - "Connection: Keep-Alive\r\n" + - "Keep-Alive: timeout=100, max=1\r\n" + - "\r\n" + - "", - - "HTTP/1.1 200 OK\r\n" + - "Content-Type: text/html; charset=utf-8\r\n" + - "Content-Length: 13\r\n" + - "Connection: Close\r\n" + - "\r\n" + - "" - }; - - private static int currentResponse; - - HttpConnectionProvider httpConnectionProvider = new HttpConnectionProvider() { - public void useProxy(ProxyInfo proxyInfo) { - } - - public HttpConnection createHttpConnection(HttpRequest httpRequest) throws IOException { - return new HttpConnection() { - @Override - public void init() throws IOException { - // ignore - } - - public OutputStream getOutputStream() throws IOException { - return new ByteArrayOutputStream(); - } - - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(RESPONSES[currentResponse].getBytes()); - } - - public void setTimeout(int milliseconds) { - // ignore - } - - public void close() { - } - }; - } - }; - - @Test - void testKeepAlive() { - currentResponse = 0; - - // -> - HttpRequest request = HttpRequest.get("http://jodd.org"); - assertEquals("Close", request.header("Connection")); - request.connectionKeepAlive(true); - assertTrue(request.isConnectionPersistent()); - - // <- - HttpResponse response = request.open(httpConnectionProvider).send(); - HttpConnection connection = request.connection(); - - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - currentResponse = 1; - - // -> - request = HttpRequest.get("http://jodd.org"); - response = request.keepAlive(response, true).send(); - - // <- - assertSame(connection, request.connection()); - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - currentResponse = 2; - - // -> LAST request - request = HttpRequest.get("http://jodd.org"); - response = request.keepAlive(response, true).send(); - - // <- - assertNull(request.connection()); // connection is closed - assertTrue(request.isConnectionPersistent()); - assertFalse(response.isConnectionPersistent()); - - currentResponse = 0; - - // -> AFTER THE LAST, STARTS EVERYTHING AGAIN - - request = HttpRequest.get("http://jodd.org"); - response = request.keepAlive(response, true).send(); // should be false for the last connection, but ok. - - // <- - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - // CLOSE - - response.close(); - assertNull(request.connection()); // connection closed - } - - @Test - void testKeepAliveBrowser() { - HttpBrowser browser = new HttpBrowser(); - browser.setKeepAlive(true); - browser.setHttpConnectionProvider(httpConnectionProvider); - - currentResponse = 0; - - // -> - HttpRequest request = HttpRequest.get("http://jodd.org"); - browser.sendRequest(request); - - // <- - HttpResponse response = browser.getHttpResponse(); - HttpConnection connection = request.connection(); - - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - currentResponse = 1; - - // -> - request = HttpRequest.get("http://jodd.org"); - response = browser.sendRequest(request); - - // <- - assertSame(connection, request.connection()); - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - currentResponse = 2; - - // -> LAST request - request = HttpRequest.get("http://jodd.org"); - response = browser.sendRequest(request); - - // <- - assertNull(request.connection()); // connection is closed - assertTrue(request.isConnectionPersistent()); - assertFalse(response.isConnectionPersistent()); - - currentResponse = 0; - - // -> AFTER THE LAST, STARTS EVERYTHING AGAIN - - request = HttpRequest.get("http://jodd.org"); - response = browser.sendRequest(request); - - // <- - assertTrue(request.isConnectionPersistent()); - assertTrue(response.isConnectionPersistent()); - assertNotNull(request.connection()); - - // CLOSE - - browser.close(); - assertNull(request.connection()); // connection closed - } -} diff --git a/jodd-http/src/test/java/jodd/http/NanoHTTPD.java b/jodd-http/src/test/java/jodd/http/NanoHTTPD.java deleted file mode 100644 index baca8ccf1..000000000 --- a/jodd-http/src/test/java/jodd/http/NanoHTTPD.java +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * #%L - * NanoHttpd-Core - * %% - * Copyright (C) 2012 - 2016 nanohttpd - * %% - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. Neither the name of the nanohttpd nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package jodd.http; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URLEncoder; -import java.util.Date; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Locale; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.TimeZone; -import java.util.Vector; - -/** - * A simple, tiny, nicely embeddable HTTP server in Java - *

- *

- * NanoHTTPD - *

- * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, - * 2010 by Konstantinos Togias - *

- *

- *

- * Features + limitations: - *

    - *

    - *

  • Only one Java file
  • - *
  • Java 5 compatible
  • - *
  • Released as open source, Modified BSD licence
  • - *
  • No fixed config files, logging, authorization etc. (Implement yourself if - * you need them.)
  • - *
  • Supports parameter parsing of GET and POST methods (+ rudimentary PUT - * support in 1.25)
  • - *
  • Supports both dynamic content and file serving
  • - *
  • Supports file upload (since version 1.2, 2010)
  • - *
  • Supports partial content (streaming)
  • - *
  • Supports ETags
  • - *
  • Never caches anything
  • - *
  • Doesn't limit bandwidth, request time or simultaneous connections
  • - *
  • Default code serves files and shows all HTTP parameters and headers
  • - *
  • File server supports directory listing, index.html and index.htm
  • - *
  • File server supports partial content (streaming)
  • - *
  • File server supports ETags
  • - *
  • File server does the 301 redirection trick for directories without '/'
  • - *
  • File server supports simple skipping for files (continue download)
  • - *
  • File server serves also very long files without memory overhead
  • - *
  • Contains a built-in list of most common MIME types
  • - *
  • All header names are converted to lower case so they don't vary between - * browsers/clients
  • - *

    - *

- *

- *

- * How to use: - *

    - *

    - *

  • Subclass and implement serve() and embed to your own program
  • - *

    - *

- *

- * See the separate "LICENSE.md" file for the distribution license (Modified BSD - * licence) - */ -public class NanoHTTPD -{ - // ================================================== - // API parts - // ================================================== - - /** - * Override this to customize the server.

- * - * (By default, this delegates to serveFile() and allows directory listing.) - * - * @param uri Percent-decoded URI without parameters, for example "/index.cgi" - * @param method "GET", "POST" etc. - * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. - * @param header Header entries, percent decoded - * @return HTTP response, see class Response for details - */ - public Response serve( String uri, String method, Properties header, Properties parms, Properties files ) - { - myOut.println( method + " '" + uri + "' " ); - - Enumeration e = header.propertyNames(); - while ( e.hasMoreElements()) - { - String value = (String)e.nextElement(); - myOut.println( " HDR: '" + value + "' = '" + - header.getProperty( value ) + "'" ); - } - e = parms.propertyNames(); - while ( e.hasMoreElements()) - { - String value = (String)e.nextElement(); - myOut.println( " PRM: '" + value + "' = '" + - parms.getProperty( value ) + "'" ); - } - e = files.propertyNames(); - while ( e.hasMoreElements()) - { - String value = (String)e.nextElement(); - myOut.println( " UPLOADED: '" + value + "' = '" + - files.getProperty( value ) + "'" ); - } - - return serveFile( uri, header, myRootDir, true ); - } - - /** - * HTTP response. - * Return one of these from serve(). - */ - public class Response - { - /** - * Default constructor: response = HTTP_OK, data = mime = 'null' - */ - public Response() - { - this.status = HTTP_OK; - } - - /** - * Basic constructor. - */ - public Response( String status, String mimeType, InputStream data ) - { - this.status = status; - this.mimeType = mimeType; - this.data = data; - } - - /** - * Convenience method that makes an InputStream out of - * given text. - */ - public Response( String status, String mimeType, String txt ) - { - this.status = status; - this.mimeType = mimeType; - try - { - this.data = new ByteArrayInputStream( txt.getBytes("UTF-8")); - } - catch ( java.io.UnsupportedEncodingException uee ) - { - uee.printStackTrace(); - } - } - - /** - * Adds given line to the header. - */ - public void addHeader( String name, String value ) - { - header.put( name, value ); - } - - /** - * HTTP status code after processing, e.g. "200 OK", HTTP_OK - */ - public String status; - - /** - * MIME type of content, e.g. "text/html" - */ - public String mimeType; - - /** - * Data of the response, may be null. - */ - public InputStream data; - - /** - * Headers for the HTTP response. Use addHeader() - * to add lines. - */ - public Properties header = new Properties(); - } - - /** - * Some HTTP response status codes - */ - public static final String - HTTP_OK = "200 OK", - HTTP_PARTIALCONTENT = "206 Partial Content", - HTTP_RANGE_NOT_SATISFIABLE = "416 Requested Range Not Satisfiable", - HTTP_REDIRECT = "301 Moved Permanently", - HTTP_NOTMODIFIED = "304 Not Modified", - HTTP_FORBIDDEN = "403 Forbidden", - HTTP_NOTFOUND = "404 Not Found", - HTTP_BADREQUEST = "400 Bad Request", - HTTP_INTERNALERROR = "500 Internal Server Error", - HTTP_NOTIMPLEMENTED = "501 Not Implemented"; - - /** - * Common mime types for dynamic content - */ - public static final String - MIME_PLAINTEXT = "text/plain", - MIME_HTML = "text/html", - MIME_DEFAULT_BINARY = "application/octet-stream", - MIME_XML = "text/xml"; - - // ================================================== - // Socket & server code - // ================================================== - - /** - * Starts a HTTP server to given port.

- * Throws an IOException if the socket is already in use - */ - public NanoHTTPD( int port, File wwwroot ) throws IOException - { - myTcpPort = port; - this.myRootDir = wwwroot; - myServerSocket = new ServerSocket( myTcpPort ); - myThread = new Thread( new Runnable() - { - @Override - public void run() - { - try - { - while( true ) - new HTTPSession( myServerSocket.accept()); - } - catch ( IOException ioe ) - {} - } - }); - myThread.setDaemon( true ); - myThread.start(); - } - - /** - * Stops the server. - */ - public void stop() - { - try - { - myServerSocket.close(); - myThread.join(); - } - catch ( IOException ioe ) {} - catch ( InterruptedException e ) {} - } - - - /** - * Starts as a standalone file server and waits for Enter. - */ - public static void main( String[] args ) - { - myOut.println( "NanoHTTPD 1.25 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n" + - "(Command line options: [-p port] [-d root-dir] [--licence])\n" ); - - // Defaults - int port = 80; - File wwwroot = new File(".").getAbsoluteFile(); - - // Show licence if requested - for ( int i=0; i 0) - { - rlen += read; - splitbyte = findHeaderEnd(buf, rlen); - if (splitbyte > 0) - break; - read = is.read(buf, rlen, bufsize - rlen); - } - } - - // Create a BufferedReader for parsing the header. - ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen); - BufferedReader hin = new BufferedReader( new InputStreamReader( hbis )); - Properties pre = new Properties(); - Properties parms = new Properties(); - Properties header = new Properties(); - Properties files = new Properties(); - - // Decode the header into parms and header java properties - decodeHeader(hin, pre, parms, header); - String method = pre.getProperty("method"); - String uri = pre.getProperty("uri"); - - long size = 0x7FFFFFFFFFFFFFFFl; - String contentLength = header.getProperty("content-length"); - if (contentLength != null) - { - try { size = Integer.parseInt(contentLength); } - catch (NumberFormatException ex) {} - } - - // Write the part of body already read to ByteArrayOutputStream f - ByteArrayOutputStream f = new ByteArrayOutputStream(); - if (splitbyte < rlen) - f.write(buf, splitbyte, rlen-splitbyte); - - // While Firefox sends on the first read all the data fitting - // our buffer, Chrome and Opera send only the headers even if - // there is data for the body. We do some magic here to find - // out whether we have already consumed part of body, if we - // have reached the end of the data to be sent or we should - // expect the first byte of the body at the next read. - if (splitbyte < rlen) - size -= rlen-splitbyte+1; - else if (splitbyte==0 || size == 0x7FFFFFFFFFFFFFFFl) - size = 0; - - // Now read all the body and write it to f - buf = new byte[512]; - while ( rlen >= 0 && size > 0 ) - { - rlen = is.read(buf, 0, 512); - size -= rlen; - if (rlen > 0) - f.write(buf, 0, rlen); - } - - // Get the raw body as a byte [] - byte [] fbuf = f.toByteArray(); - - // Create a BufferedReader for easily reading it as string. - ByteArrayInputStream bin = new ByteArrayInputStream(fbuf); - BufferedReader in = new BufferedReader( new InputStreamReader(bin)); - - // If the method is POST, there may be parameters - // in data section, too, read it: - if ( method.equalsIgnoreCase( "POST" )) - { - String contentType = ""; - String contentTypeHeader = header.getProperty("content-type"); - StringTokenizer st = new StringTokenizer( contentTypeHeader , "; " ); - if ( st.hasMoreTokens()) { - contentType = st.nextToken(); - } - - if (contentType.equalsIgnoreCase("multipart/form-data")) - { - // Handle multipart/form-data - if ( !st.hasMoreTokens()) - sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html" ); - String boundaryExp = st.nextToken(); - st = new StringTokenizer( boundaryExp , "=" ); - if (st.countTokens() != 2) - sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html" ); - st.nextToken(); - String boundary = st.nextToken(); - - decodeMultipartData(boundary, fbuf, in, parms, files); - } - else - { - // Handle application/x-www-form-urlencoded - String postLine = ""; - char[] pbuf = new char[512]; - int read = in.read(pbuf); - while ( read >= 0 && !postLine.endsWith("\r\n") ) - { - postLine += String.valueOf(pbuf, 0, read); - read = in.read(pbuf); - } - postLine = postLine.trim(); - decodeParms( postLine, parms ); - } - } - - if ( method.equalsIgnoreCase( "PUT" )) - files.put("content", saveTmpFile( fbuf, 0, f.size())); - - // Ok, now do the serve() - Response r = serve( uri, method, header, parms, files ); - if ( r == null ) - sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." ); - else - sendResponse( r.status, r.mimeType, r.header, r.data ); - - in.close(); - is.close(); - } - catch ( IOException ioe ) - { - try - { - sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } - catch ( Throwable t ) {} - } - catch ( InterruptedException ie ) - { - // Thrown by sendError, ignore and exit the thread. - } - } - - /** - * Decodes the sent headers and loads the data into - * java Properties' key - value pairs - **/ - private void decodeHeader(BufferedReader in, Properties pre, Properties parms, Properties header) - throws InterruptedException - { - try { - // Read the request line - String inLine = in.readLine(); - if (inLine == null) return; - StringTokenizer st = new StringTokenizer( inLine ); - if ( !st.hasMoreTokens()) - sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" ); - - String method = st.nextToken(); - pre.put("method", method); - - if ( !st.hasMoreTokens()) - sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" ); - - String uri = st.nextToken(); - - // Decode parameters from the URI - int qmi = uri.indexOf( '?' ); - if ( qmi >= 0 ) - { - decodeParms( uri.substring( qmi+1 ), parms ); - uri = decodePercent( uri.substring( 0, qmi )); - } - else uri = decodePercent(uri); - - // If there's another token, it's protocol version, - // followed by HTTP headers. Ignore version but parse headers. - // NOTE: this now forces header names lowercase since they are - // case insensitive and vary by client. - if ( st.hasMoreTokens()) - { - String line = in.readLine(); - while ( line != null && line.trim().length() > 0 ) - { - int p = line.indexOf( ':' ); - if ( p >= 0 ) - header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim()); - line = in.readLine(); - } - } - - pre.put("uri", uri); - } - catch ( IOException ioe ) - { - sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } - } - - /** - * Decodes the Multipart Body data and put it - * into java Properties' key - value pairs. - **/ - private void decodeMultipartData(String boundary, byte[] fbuf, BufferedReader in, Properties parms, Properties files) - throws InterruptedException - { - try - { - int[] bpositions = getBoundaryPositions(fbuf,boundary.getBytes()); - int boundarycount = 1; - String mpline = in.readLine(); - while ( mpline != null ) - { - if (mpline.indexOf(boundary) == -1) - sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html" ); - boundarycount++; - Properties item = new Properties(); - mpline = in.readLine(); - while (mpline != null && mpline.trim().length() > 0) - { - int p = mpline.indexOf( ':' ); - if (p != -1) - item.put( mpline.substring(0,p).trim().toLowerCase(), mpline.substring(p+1).trim()); - mpline = in.readLine(); - } - if (mpline != null) - { - String contentDisposition = item.getProperty("content-disposition"); - if (contentDisposition == null) - { - sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html" ); - } - StringTokenizer st = new StringTokenizer( contentDisposition , "; " ); - Properties disposition = new Properties(); - while ( st.hasMoreTokens()) - { - String token = st.nextToken(); - int p = token.indexOf( '=' ); - if (p!=-1) - disposition.put( token.substring(0,p).trim().toLowerCase(), token.substring(p+1).trim()); - } - String pname = disposition.getProperty("name"); - pname = pname.substring(1,pname.length()-1); - - String value = ""; - if (item.getProperty("content-type") == null) { - while (mpline != null && mpline.indexOf(boundary) == -1) - { - mpline = in.readLine(); - if ( mpline != null) - { - int d = mpline.indexOf(boundary); - if (d == -1) - value+=mpline; - else - value+=mpline.substring(0,d-2); - } - } - } - else - { - if (boundarycount> bpositions.length) - sendError( HTTP_INTERNALERROR, "Error processing request" ); - int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount-2]); - String path = saveTmpFile(fbuf, offset, bpositions[boundarycount-1]-offset-4); - files.put(pname, path); - value = disposition.getProperty("filename"); - value = value.substring(1,value.length()-1); - do { - mpline = in.readLine(); - } while (mpline != null && mpline.indexOf(boundary) == -1); - } - parms.put(pname, value); - } - } - } - catch ( IOException ioe ) - { - sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } - } - - /** - * Find byte index separating header from body. - * It must be the last byte of the first two sequential new lines. - **/ - private int findHeaderEnd(final byte[] buf, int rlen) - { - int splitbyte = 0; - while (splitbyte + 3 < rlen) - { - if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') - return splitbyte + 4; - splitbyte++; - } - return 0; - } - - /** - * Find the byte positions where multipart boundaries start. - **/ - public int[] getBoundaryPositions(byte[] b, byte[] boundary) - { - int matchcount = 0; - int matchbyte = -1; - Vector matchbytes = new Vector(); - for (int i=0; i 0) - { - String tmpdir = System.getProperty("java.io.tmpdir"); - try { - File temp = File.createTempFile("NanoHTTPD", "", new File(tmpdir)); - OutputStream fstream = new FileOutputStream(temp); - fstream.write(b, offset, len); - fstream.close(); - path = temp.getAbsolutePath(); - } catch (Exception e) { // Catch exception if any - myErr.println("Error: " + e.getMessage()); - } - } - return path; - } - - - /** - * It returns the offset separating multipart file headers - * from the file's data. - **/ - private int stripMultipartHeaders(byte[] b, int offset) - { - int i = 0; - for (i=offset; i "an example string"} - */ - private String decodePercent( String str ) throws InterruptedException - { - try - { - StringBuffer sb = new StringBuffer(); - for( int i=0; i0) - { - int read = data.read( buff, 0, ( (pending>theBufferSize) ? theBufferSize : pending )); - if (read <= 0) break; - out.write( buff, 0, read ); - pending -= read; - } - } - out.flush(); - out.close(); - if ( data != null ) - data.close(); - } - catch( IOException ioe ) - { - // Couldn't write? No can do. - try { mySocket.close(); } catch( Throwable t ) {} - } - } - - private Socket mySocket; - } - - /** - * URL-encodes everything between "/"-characters. - * Encodes spaces as '%20' instead of '+'. - */ - private String encodeUri( String uri ) - { - String newUri = ""; - StringTokenizer st = new StringTokenizer( uri, "/ ", true ); - while ( st.hasMoreTokens()) - { - String tok = st.nextToken(); - if ( tok.equals( "/" )) - newUri += "/"; - else if ( tok.equals( " " )) - newUri += "%20"; - else - { - newUri += URLEncoder.encode( tok ); - // For Java 1.4 you'll want to use this instead: - // try { newUri += URLEncoder.encode( tok, "UTF-8" ); } catch ( java.io.UnsupportedEncodingException uee ) {} - } - } - return newUri; - } - - private int myTcpPort; - private final ServerSocket myServerSocket; - private Thread myThread; - private File myRootDir; - - // ================================================== - // File server code - // ================================================== - - /** - * Serves file from homeDir and its' subdirectories (only). - * Uses only URI, ignores all headers and HTTP parameters. - */ - public Response serveFile( String uri, Properties header, File homeDir, - boolean allowDirectoryListing ) - { - Response res = null; - - // Make sure we won't die of an exception later - if ( !homeDir.isDirectory()) - res = new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT, - "INTERNAL ERRROR: serveFile(): given homeDir is not a directory." ); - - if ( res == null ) - { - // Remove URL arguments - uri = uri.trim().replace( File.separatorChar, '/' ); - if ( uri.indexOf( '?' ) >= 0 ) - uri = uri.substring(0, uri.indexOf( '?' )); - - // Prohibit getting out of current directory - if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ) - res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, - "FORBIDDEN: Won't serve ../ for security reasons." ); - } - - File f = new File( homeDir, uri ); - if ( res == null && !f.exists()) - res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT, - "Error 404, file not found." ); - - // List the directory, if necessary - if ( res == null && f.isDirectory()) - { - // Browsers get confused without '/' after the - // directory, send a redirect. - if ( !uri.endsWith( "/" )) - { - uri += "/"; - res = new Response( HTTP_REDIRECT, MIME_HTML, - "Redirected: " + - uri + ""); - res.addHeader( "Location", uri ); - } - - if ( res == null ) - { - // First try index.html and index.htm - if ( new File( f, "index.html" ).exists()) - f = new File( homeDir, uri + "/index.html" ); - else if ( new File( f, "index.htm" ).exists()) - f = new File( homeDir, uri + "/index.htm" ); - // No index file, list the directory if it is readable - else if ( allowDirectoryListing && f.canRead() ) - { - String[] files = f.list(); - String msg = "

Directory " + uri + "


"; - - if ( uri.length() > 1 ) - { - String u = uri.substring( 0, uri.length()-1 ); - int slash = u.lastIndexOf( '/' ); - if ( slash >= 0 && slash < u.length()) - msg += "..
"; - } - - if (files!=null) - { - for ( int i=0; i" + - files[i] + ""; - - // Show file size - if ( curFile.isFile()) - { - long len = curFile.length(); - msg += "  ("; - if ( len < 1024 ) - msg += len + " bytes"; - else if ( len < 1024 * 1024 ) - msg += len/1024 + "." + (len%1024/10%100) + " KB"; - else - msg += len/(1024*1024) + "." + len%(1024*1024)/10%100 + " MB"; - - msg += ")"; - } - msg += "
"; - if ( dir ) msg += ""; - } - } - msg += ""; - res = new Response( HTTP_OK, MIME_HTML, msg ); - } - else - { - res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, - "FORBIDDEN: No directory listing." ); - } - } - } - - try - { - if ( res == null ) - { - // Get MIME type from file name extension, if possible - String mime = null; - int dot = f.getCanonicalPath().lastIndexOf( '.' ); - if ( dot >= 0 ) - mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase()); - if ( mime == null ) - mime = MIME_DEFAULT_BINARY; - - // Calculate etag - String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode()); - - // Support (simple) skipping: - long startFrom = 0; - long endAt = -1; - String range = header.getProperty( "range" ); - if ( range != null ) - { - if ( range.startsWith( "bytes=" )) - { - range = range.substring( "bytes=".length()); - int minus = range.indexOf( '-' ); - try { - if ( minus > 0 ) - { - startFrom = Long.parseLong( range.substring( 0, minus )); - endAt = Long.parseLong( range.substring( minus+1 )); - } - } - catch ( NumberFormatException nfe ) {} - } - } - - // Change return code and add Content-Range header when skipping is requested - long fileLen = f.length(); - if (range != null && startFrom >= 0) - { - if ( startFrom >= fileLen) - { - res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" ); - res.addHeader( "Content-Range", "bytes 0-0/" + fileLen); - res.addHeader( "ETag", etag); - } - else - { - if ( endAt < 0 ) - endAt = fileLen-1; - long newLen = endAt - startFrom + 1; - if ( newLen < 0 ) newLen = 0; - - final long dataLen = newLen; - FileInputStream fis = new FileInputStream( f ) { - @Override - public int available() throws IOException { return (int)dataLen; } - }; - fis.skip( startFrom ); - - res = new Response( HTTP_PARTIALCONTENT, mime, fis ); - res.addHeader( "Content-Length", "" + dataLen); - res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen); - res.addHeader( "ETag", etag); - } - } - else - { - if (etag.equals(header.getProperty("if-none-match"))) - res = new Response( HTTP_NOTMODIFIED, mime, ""); - else - { - res = new Response( HTTP_OK, mime, new FileInputStream( f )); - res.addHeader( "Content-Length", "" + fileLen); - res.addHeader( "ETag", etag); - } - } - } - } - catch( IOException ioe ) - { - res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." ); - } - - res.addHeader( "Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes - return res; - } - - /** - * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE - */ - private static Hashtable theMimeTypes = new Hashtable(); - static - { - StringTokenizer st = new StringTokenizer( - "css text/css "+ - "htm text/html "+ - "html text/html "+ - "xml text/xml "+ - "txt text/plain "+ - "asc text/plain "+ - "gif image/gif "+ - "jpg image/jpeg "+ - "jpeg image/jpeg "+ - "png image/png "+ - "mp3 audio/mpeg "+ - "m3u audio/mpeg-url " + - "mp4 video/mp4 " + - "ogv video/ogg " + - "flv video/x-flv " + - "mov video/quicktime " + - "swf application/x-shockwave-flash " + - "js application/javascript "+ - "pdf application/pdf "+ - "doc application/msword "+ - "ogg application/x-ogg "+ - "zip application/octet-stream "+ - "exe application/octet-stream "+ - "class application/octet-stream " ); - while ( st.hasMoreTokens()) - theMimeTypes.put( st.nextToken(), st.nextToken()); - } - - private static int theBufferSize = 16 * 1024; - - // Change these if you want to log to somewhere else than stdout - protected static PrintStream myOut = System.out; - protected static PrintStream myErr = System.err; - - /** - * GMT date formatter - */ - private static java.text.SimpleDateFormat gmtFrmt; - static - { - gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); - gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); - } - - /** - * The distribution licence - */ - private static final String LICENCE = - "Copyright (C) 2001,2005-2011 by Jarno Elonen \n"+ - "and Copyright (C) 2010 by Konstantinos Togias \n"+ - "\n"+ - "Redistribution and use in source and binary forms, with or without\n"+ - "modification, are permitted provided that the following conditions\n"+ - "are met:\n"+ - "\n"+ - "Redistributions of source code must retain the above copyright notice,\n"+ - "this list of conditions and the following disclaimer. Redistributions in\n"+ - "binary form must reproduce the above copyright notice, this list of\n"+ - "conditions and the following disclaimer in the documentation and/or other\n"+ - "materials provided with the distribution. The name of the author may not\n"+ - "be used to endorse or promote products derived from this software without\n"+ - "specific prior written permission. \n"+ - " \n"+ - "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+ - "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+ - "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+ - "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+ - "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+ - "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+ - "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+ - "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+ - "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+ - "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; -} \ No newline at end of file diff --git a/jodd-http/src/test/java/jodd/http/ProxyTest.java b/jodd-http/src/test/java/jodd/http/ProxyTest.java deleted file mode 100644 index 9c818ca10..000000000 --- a/jodd-http/src/test/java/jodd/http/ProxyTest.java +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import io.netty.handler.codec.http.HttpHeaders; -import jodd.http.net.SocketHttpConnectionProvider; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.mockserver.integration.ClientAndProxy; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.Header; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockserver.integration.ClientAndProxy.startClientAndProxy; -import static org.mockserver.integration.ClientAndServer.startClientAndServer; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; -import static org.mockserver.verify.VerificationTimes.exactly; - -class ProxyTest { - - private ClientAndProxy proxy; - private ClientAndServer mockServer; - - @BeforeEach - void startProxy() { - mockServer = startClientAndServer(1080); - proxy = startClientAndProxy(1090); - setupMockServer(); - } - - @AfterEach - void stopProxy() { - proxy.stop(); - mockServer.stop(); - } - - @Test - void testDirect() { - HttpResponse response = HttpRequest.get("http://localhost:1080/get_books").send(); - assertEquals(200, response.statusCode()); - assertTrue(response.body().contains("Tatum")); - proxy.verify(request().withPath("/get_books"), exactly(0)); - } - - @Test - void testDirectHttps() { - HttpResponse response = HttpRequest.get("https://localhost:1080/get_books").trustAllCerts(true).send(); - assertEquals(200, response.statusCode()); - assertTrue(response.body().contains("Tatum")); - proxy.verify(request().withPath("/get_books"), exactly(0)); - } - - @Test - @Disabled - void testHttpProxy() { - SocketHttpConnectionProvider s = new SocketHttpConnectionProvider(); - s.useProxy(ProxyInfo.httpProxy("localhost", 1090, null, null)); - - HttpResponse response = HttpRequest.get("http://localhost:1080/get_books") - .withConnectionProvider(s) - .send(); - assertEquals(200, response.statusCode()); - assertTrue(response.body().contains("Tatum")); - } - - @Test - void testSocks5Proxy() { - SocketHttpConnectionProvider s = new SocketHttpConnectionProvider(); - s.useProxy(ProxyInfo.socks5Proxy("localhost", 1090, null, null)); - - HttpResponse response = HttpRequest.get("http://localhost:1080/get_books") - .withConnectionProvider(s) - .send(); - assertEquals(200, response.statusCode()); - assertTrue(response.body().contains("Tatum")); - proxy.verify(request().withPath("/get_books"), exactly(1)); - } - - @Test - void testSocks5ProxyWithHttps() { - SocketHttpConnectionProvider s = new SocketHttpConnectionProvider(); - s.useProxy(ProxyInfo.socks5Proxy("localhost", 1090, null, null)); - - HttpResponse response = HttpRequest.get("https://localhost:1080/get_books") - .withConnectionProvider(s) - .trustAllCerts(true) - .send(); - assertEquals(200, response.statusCode()); - assertTrue(response.body().contains("Tatum")); - proxy.verify(request().withPath("/get_books"), exactly(1)); - } - - private void setupMockServer() { - mockServer - .when( - request() - .withPath("/get_books") - ) - .respond( - response() - .withHeaders( - new Header(HttpHeaders.Names.CONTENT_TYPE,"application/json") - ) - .withBody("" + - "[\n" + - " {\n" + - " \"id\": \"1\",\n" + - " \"title\": \"Xenophon's imperial fiction : on the education of Cyrus\",\n" + - " \"author\": \"James Tatum\",\n" + - " \"isbn\": \"0691067570\",\n" + - " \"publicationDate\": \"1989\"\n" + - " },\n" + - " {\n" + - " \"id\": \"2\",\n" + - " \"title\": \"You are here : personal geographies and other maps of the imagination\",\n" + - " \"author\": \"Katharine A. Harmon\",\n" + - " \"isbn\": \"1568984308\",\n" + - " \"publicationDate\": \"2004\"\n" + - " },\n" + - " {\n" + - " \"id\": \"3\",\n" + - " \"title\": \"You just don't understand : women and men in conversation\",\n" + - " \"author\": \"Deborah Tannen\",\n" + - " \"isbn\": \"0345372050\",\n" + - " \"publicationDate\": \"1990\"\n" + - " }" + - "]") - ); - } -} diff --git a/jodd-http/src/test/java/jodd/http/RawTest.java b/jodd-http/src/test/java/jodd/http/RawTest.java deleted file mode 100644 index 81e79e0d7..000000000 --- a/jodd-http/src/test/java/jodd/http/RawTest.java +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.FileUtil; -import jodd.util.StringUtil; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URL; - -import static org.junit.jupiter.api.Assertions.*; - -class RawTest { - - @Test - void testRawResponse1() throws IOException { - URL data = RawTest.class.getResource("1-response.txt"); - - String fileContent = FileUtil.readString(data.getFile()); - - fileContent = StringUtil.replace(fileContent, "\r\n", "\n"); - - HttpResponse response = HttpResponse.readFrom(new ByteArrayInputStream(fileContent.getBytes("UTF-8"))); - - HttpMultiMap headers = response.headers; - assertEquals(7, headers.size()); - - assertEquals("no-cache", headers.get("pragma")); - assertEquals("Sat, 23 Mar 2013 23:34:18 GMT", headers.get("date")); - assertEquals("max-age=0, must-revalidate, no-cache, no-store, private, post-check=0, pre-check=0", - headers.get("cache-control")); - assertEquals("no-cache", headers.get("pragma")); - assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", headers.get("expires")); - assertEquals("text/html;charset=UTF-8", headers.get("content-type")); - assertEquals("close", headers.get("connection")); - assertEquals("102", headers.get("content-length")); - - assertEquals("no-cache", response.header("Pragma")); - assertEquals("text/html;charset=UTF-8" , response.contentType()); - assertEquals("text/html" , response.mediaType()); - assertEquals("UTF-8" , response.charset()); - - assertNotNull(response.contentLength()); - - String rawBody = response.body(); - String textBody = response.bodyText(); - - assertTrue(rawBody.startsWith("")); - assertTrue(rawBody.endsWith("")); - - assertTrue(rawBody.contains("This is UTF8 Encoding.")); - assertFalse(rawBody.contains("Тхис ис УТФ8 Енцодинг.")); - assertTrue(textBody.contains("Тхис ис УТФ8 Енцодинг.")); - - assertEquals(77, textBody.length()); - - int len = textBody.getBytes("UTF-8").length; - - assertEquals(94, rawBody.length()); - assertEquals(len, rawBody.length()); - } - - @Test - void testRawResponse4() throws IOException { - URL data = RawTest.class.getResource("4-response.txt"); - - String fileContent = FileUtil.readString(data.getFile()); - - fileContent = StringUtil.replace(fileContent, "\n", "\r\n"); - fileContent = StringUtil.replace(fileContent, "\r\r\n", "\r\n"); - - HttpResponse response = HttpResponse.readFrom(new ByteArrayInputStream(fileContent.getBytes("UTF-8"))); - - String body = response.bodyText(); - - assertEquals( - "Wikipedia in\n" + - "\n" + - "chunks.", body.replace("\r\n", "\n")); - } - - - @Test - void testRawResponse5() throws IOException { - URL data = RawTest.class.getResource("5-response.txt"); - - String fileContent = FileUtil.readString(data.getFile()); - - fileContent = StringUtil.replace(fileContent, "\n", "\r\n"); - fileContent = StringUtil.replace(fileContent, "\r\r\n", "\r\n"); - - HttpResponse response = HttpResponse.readFrom(new ByteArrayInputStream(fileContent.getBytes("UTF-8"))); - - String body = response.bodyText(); - - assertEquals( - "Wikipedia in\n" + - "\n" + - "chunks.", body.replace("\r\n", "\n")); - - assertEquals("TheData", response.header("SomeAfterHeader")); - } - - @Test - void testRawResponse6() throws IOException { - URL data = RawTest.class.getResource("6-response.txt"); - - String fileContent = FileUtil.readString(data.getFile()); - - fileContent = StringUtil.replace(fileContent, "\n", "\r\n"); - fileContent = StringUtil.replace(fileContent, "\r\r\n", "\r\n"); - - HttpResponse response = HttpResponse.readFrom(new ByteArrayInputStream(fileContent.getBytes("UTF-8"))); - - assertEquals(200, response.statusCode()); - assertEquals("", response.statusPhrase); - - String body = response.bodyText(); - - assertEquals( - "Wikipedia in\n" + - "\n" + - "chunks.", body.replace("\r\n", "\n")); - - assertEquals("TheData", response.header("SomeAfterHeader")); - } - - -} diff --git a/jodd-http/src/test/java/jodd/http/TestServer.java b/jodd-http/src/test/java/jodd/http/TestServer.java deleted file mode 100644 index e9bc74f2d..000000000 --- a/jodd-http/src/test/java/jodd/http/TestServer.java +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import jodd.io.FileUtil; - -import java.io.File; -import java.net.URL; - - -/** - * Common server content. - */ -public abstract class TestServer { - - protected File webRoot; - - public void start() throws Exception { - webRoot = FileUtil.createTempDirectory("jodd-http", "test"); - webRoot.deleteOnExit(); - - // web-inf - - File webInfFolder = new File(webRoot, "WEB-INF"); - webInfFolder.mkdir(); - - // web.xml - - URL webXmlUrl = TestServer.class.getResource("web.xml"); - File webXmlFile = FileUtil.toFile(webXmlUrl); - - FileUtil.copy(webXmlFile, webInfFolder); - - // lib folder - - File libFolder = new File(webInfFolder, "lib"); - libFolder.mkdir(); - - // classes - - File classes = new File(webInfFolder, "classes/jodd/http/fixture"); - classes.mkdirs(); - - URL echoServletUrl = TestServer.class.getResource("fixture/EchoServlet.class"); - File echoServletFile = FileUtil.toFile(echoServletUrl); - FileUtil.copyFileToDir(echoServletFile, classes); - - echoServletUrl = TestServer.class.getResource("fixture/Echo2Servlet.class"); - echoServletFile = FileUtil.toFile(echoServletUrl); - FileUtil.copyFileToDir(echoServletFile, classes); - - echoServletUrl = TestServer.class.getResource("fixture/Echo3Servlet.class"); - echoServletFile = FileUtil.toFile(echoServletUrl); - FileUtil.copyFileToDir(echoServletFile, classes); - - URL redirectServletUrl = TestServer.class.getResource("fixture/RedirectServlet.class"); - File redirectServletFile = FileUtil.toFile(redirectServletUrl); - FileUtil.copyFileToDir(redirectServletFile, classes); - - URL targetServletUrl = TestServer.class.getResource("fixture/TargetServlet.class"); - File targetServletFile = FileUtil.toFile(targetServletUrl); - FileUtil.copyFileToDir(targetServletFile, classes); - } - - public void stop() throws Exception { - webRoot.delete(); - } -} \ No newline at end of file diff --git a/jodd-http/src/test/java/jodd/http/TimeoutTest.java b/jodd-http/src/test/java/jodd/http/TimeoutTest.java deleted file mode 100644 index ff3df7f92..000000000 --- a/jodd-http/src/test/java/jodd/http/TimeoutTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -class TimeoutTest { - - static TestServer testServer; - - @BeforeAll - static void startServer() throws Exception { - testServer = new TomcatServer(); - testServer.start(); - } - - @AfterAll - static void stopServer() throws Exception { - testServer.stop(); - } - - @Test - void testTimeout() { - HttpRequest httpRequest = HttpRequest.get("localhost:8173/slow"); - httpRequest.timeout(1000); - - try { - httpRequest.send(); - fail("error"); - } - catch(HttpException ignore) { - } - - httpRequest = HttpRequest.get("localhost:8173/slow"); - httpRequest.timeout(6000); - - int status = httpRequest.send().statusCode(); - - assertEquals(200, status); - } -} diff --git a/jodd-http/src/test/java/jodd/http/TomcatServer.java b/jodd-http/src/test/java/jodd/http/TomcatServer.java deleted file mode 100644 index 74f964cb7..000000000 --- a/jodd-http/src/test/java/jodd/http/TomcatServer.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http; - -import org.apache.catalina.startup.Tomcat; - -/** - * Tomcat Server. - */ -public class TomcatServer extends TestServer { - - protected Tomcat tomcat; - - @Override - public void start() throws Exception { - super.start(); - - String workingDir = System.getProperty("java.io.tmpdir"); - - tomcat = new Tomcat(); - tomcat.setPort(8173); - tomcat.setBaseDir(workingDir); - tomcat.addWebapp("", webRoot.getAbsolutePath()); - - tomcat.start(); - } - - @Override - public void stop() throws Exception { - tomcat.stop(); - tomcat.destroy(); - super.stop(); - } -} \ No newline at end of file diff --git a/jodd-http/src/test/java/jodd/http/fixture/Data.java b/jodd-http/src/test/java/jodd/http/fixture/Data.java deleted file mode 100644 index 42d4f18c8..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/Data.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import javax.servlet.http.Cookie; -import java.util.Map; - -public class Data { - - public static Data ref; - - public boolean get; - public boolean post; - public String queryString; - public String body; - public Map header; - public Map params; - public Map parts; - public Map fileNames; - public Cookie[] cookies; -} diff --git a/jodd-http/src/test/java/jodd/http/fixture/Echo2Servlet.java b/jodd-http/src/test/java/jodd/http/fixture/Echo2Servlet.java deleted file mode 100644 index 1d64cc33f..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/Echo2Servlet.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import javax.servlet.annotation.MultipartConfig; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -@MultipartConfig -public class Echo2Servlet extends EchoServlet { - - @Override - protected void readAll(final HttpServletRequest req) throws IOException { - Data.ref.queryString = req.getQueryString(); - Data.ref.header = copyHeaders(req); - Data.ref.params = copyParams(req, StandardCharsets.UTF_8.name()); - Data.ref.parts = copyParts(req); - Data.ref.fileNames = copyFileName(req); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/fixture/Echo3Servlet.java b/jodd-http/src/test/java/jodd/http/fixture/Echo3Servlet.java deleted file mode 100644 index 559d5ee11..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/Echo3Servlet.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import javax.servlet.annotation.MultipartConfig; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -@MultipartConfig -public class Echo3Servlet extends EchoServlet { - - @Override - protected void readAll(final HttpServletRequest req) throws IOException { - Data.ref.queryString = req.getQueryString(); - Data.ref.header = copyHeaders(req); - Data.ref.params = copyParams(req, StandardCharsets.ISO_8859_1.name()); - Data.ref.parts = copyParts(req); - Data.ref.fileNames = copyFileName(req); - } - -} diff --git a/jodd-http/src/test/java/jodd/http/fixture/EchoServlet.java b/jodd-http/src/test/java/jodd/http/fixture/EchoServlet.java deleted file mode 100644 index 372c84129..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/EchoServlet.java +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import jodd.io.IOUtil; -import jodd.util.StringUtil; -import org.apache.catalina.core.ApplicationPart; - -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Part; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -public class EchoServlet extends HttpServlet { - - @Override - protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - Data.ref = new Data(); - Data.ref.get = true; - Data.ref.post = false; - readAll(req); - - if (Data.ref.cookies != null) { - for (final Cookie cookie : Data.ref.cookies) { - cookie.setValue(cookie.getValue() + "!"); - resp.addCookie(cookie); - } - } - - write(resp, Data.ref.body); - } - - @Override - protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - Data.ref = new Data(); - Data.ref.post = true; - Data.ref.get = false; - readAll(req); - write(resp, Data.ref.body); - } - - // ---------------------------------------------------------------- write - - protected void write(final HttpServletResponse resp, final String text) throws IOException { - if (text != null) { - resp.setContentLength(text.getBytes(StandardCharsets.UTF_8).length); - resp.setContentType("text/html;charset=UTF-8"); - resp.getWriter().write(text); - resp.flushBuffer(); - } - } - - // ---------------------------------------------------------------- read all - - protected void readAll(final HttpServletRequest req) throws IOException { - Data.ref.body = readRequestBody(req); - Data.ref.queryString = req.getQueryString(); - Data.ref.header = copyHeaders(req); - Data.ref.cookies = req.getCookies(); - } - - protected String readRequestBody(final HttpServletRequest request) throws IOException { - final BufferedReader buff = request.getReader(); - final StringWriter out = new StringWriter(); - IOUtil.copy(buff, out); - return out.toString(); - } - - protected Map copyHeaders(final HttpServletRequest req) { - final Enumeration enumeration = req.getHeaderNames(); - final Map header = new HashMap<>(); - - while (enumeration.hasMoreElements()) { - final String name = enumeration.nextElement().toString(); - final String value = req.getHeader(name); - header.put(name, value); - } - - return header; - } - - protected Map copyParams(final HttpServletRequest req, final String fromEncoding) { - final String charset = req.getParameter("enc"); - - final Enumeration enumeration = req.getParameterNames(); - final Map params = new HashMap<>(); - - while (enumeration.hasMoreElements()) { - final String name = enumeration.nextElement().toString(); - String value = req.getParameter(name); - if (charset != null) { - value = StringUtil.convertCharset(value, Charset.forName(fromEncoding), Charset.forName(charset)); - } - params.put(name, value); - } - - return params; - } - - protected Map copyParts(final HttpServletRequest req) { - final Map parts = new HashMap<>(); - if (req.getContentType() == null) { - return parts; - } - if (req.getContentType() != null && !req.getContentType().toLowerCase().contains("multipart/form-data")) { - return parts; - } - - final String enc = "UTF-8"; - - try { - final Collection prs = req.getParts(); - - for (final Part p : prs) { - parts.put(p.getName(), new String(IOUtil.readBytes(p.getInputStream()), enc)); - } - } - catch (final IOException | ServletException e) { - e.printStackTrace(); - } - - return parts; - } - - protected Map copyFileName(final HttpServletRequest req) { - final Map parts = new HashMap<>(); - if (req.getContentType() == null) { - return parts; - } - if (req.getContentType() != null && !req.getContentType().toLowerCase().contains("multipart/form-data")) { - return parts; - } - - try { - final Collection prs = req.getParts(); - - for (final Part p : prs) { - if (p instanceof ApplicationPart) { - final ApplicationPart ap = (ApplicationPart) p; - parts.put(p.getName(), ap.getSubmittedFileName()); - } - } - } - catch (final IOException | ServletException e) { - e.printStackTrace(); - } - - return parts; - } - -} diff --git a/jodd-http/src/test/java/jodd/http/fixture/RedirectServlet.java b/jodd-http/src/test/java/jodd/http/fixture/RedirectServlet.java deleted file mode 100644 index 62dabd6da..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/RedirectServlet.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * Simply redirects to '/target'. - */ -public class RedirectServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - - resp.sendRedirect("/target"); - } -} \ No newline at end of file diff --git a/jodd-http/src/test/java/jodd/http/fixture/SlowServlet.java b/jodd-http/src/test/java/jodd/http/fixture/SlowServlet.java deleted file mode 100644 index e9f6e9fb9..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/SlowServlet.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import jodd.util.ThreadUtil; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -public class SlowServlet extends HttpServlet { - - @Override - protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - ThreadUtil.sleep(5000); - write(resp, "OK"); - } - - protected void write(final HttpServletResponse resp, final String text) throws IOException { - if (text != null) { - resp.setContentLength(text.getBytes(StandardCharsets.UTF_8.name()).length); - resp.setContentType("text/html;charset=UTF-8"); - resp.getWriter().write(text); - resp.flushBuffer(); - } - } - -} diff --git a/jodd-http/src/test/java/jodd/http/fixture/TargetServlet.java b/jodd-http/src/test/java/jodd/http/fixture/TargetServlet.java deleted file mode 100644 index 69e0d1c94..000000000 --- a/jodd-http/src/test/java/jodd/http/fixture/TargetServlet.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.http.fixture; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class TargetServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - - resp.getWriter().write("target!"); - } - - -} \ No newline at end of file diff --git a/jodd-http/src/test/resources/jodd/http/1-response.txt b/jodd-http/src/test/resources/jodd/http/1-response.txt deleted file mode 100644 index 14b948a27..000000000 --- a/jodd-http/src/test/resources/jodd/http/1-response.txt +++ /dev/null @@ -1,17 +0,0 @@ -HTTP/1.1 200 OK -Date: Sat, 23 Mar 2013 23:34:18 GMT -Cache-Control: max-age=0, must-revalidate, no-cache, no-store, private, post-check=0, pre-check=0 -Pragma: no-cache -Expires: Thu, 01 Jan 1970 00:00:00 GMT -Content-Type: text/html;charset=UTF-8 -Connection: close -Content-Length: 102 - - - - -This is UTF8 Encoding. -Тхис ис УТФ8 Енцодинг. - - - \ No newline at end of file diff --git a/jodd-http/src/test/resources/jodd/http/2-response.txt b/jodd-http/src/test/resources/jodd/http/2-response.txt deleted file mode 100644 index 5fc7e7b26..000000000 --- a/jodd-http/src/test/resources/jodd/http/2-response.txt +++ /dev/null @@ -1,12 +0,0 @@ -HTTP/1.1 200 OK -Content-Type: application/json; charset=UTF-8 -Date: Thu, 27 Feb 2014 21:10:55 GMT -Expires: Fri, 28 Feb 2014 21:10:55 GMT -Cache-Control: public, max-age=86400 -Vary: Accept-Language -Access-Control-Allow-Origin: * -Server: mafe -X-Xss-Protection: 1; mode=block -X-Frame-Options: SAMEORIGIN -Alternate-Protocol: 80:quic -Connection: close \ No newline at end of file diff --git a/jodd-http/src/test/resources/jodd/http/3-response.txt b/jodd-http/src/test/resources/jodd/http/3-response.txt deleted file mode 100644 index 6b3694306..000000000 --- a/jodd-http/src/test/resources/jodd/http/3-response.txt +++ /dev/null @@ -1,14 +0,0 @@ -HTTP/1.1 200 OK -Content-Type: application/json; charset=UTF-8 -Date: Thu, 27 Feb 2014 21:10:55 GMT -Expires: Fri, 28 Feb 2014 21:10:55 GMT -Cache-Control: public, max-age=86400 -Vary: Accept-Language -Access-Control-Allow-Origin: * -Server: mafe -X-Xss-Protection: 1; mode=block -X-Frame-Options: SAMEORIGIN -Alternate-Protocol: 80:quic -Connection: close - -Body! \ No newline at end of file diff --git a/jodd-http/src/test/resources/jodd/http/4-response.txt b/jodd-http/src/test/resources/jodd/http/4-response.txt deleted file mode 100644 index 5e0b208df..000000000 --- a/jodd-http/src/test/resources/jodd/http/4-response.txt +++ /dev/null @@ -1,14 +0,0 @@ -HTTP/1.1 200 OK -content-type:text/html; charset=utf-8 -Transfer-Encoding: chunked -Connection: close - -4 -Wiki -5 -pedia -e - in - -chunks. -0 diff --git a/jodd-http/src/test/resources/jodd/http/5-response.txt b/jodd-http/src/test/resources/jodd/http/5-response.txt deleted file mode 100644 index 9865db525..000000000 --- a/jodd-http/src/test/resources/jodd/http/5-response.txt +++ /dev/null @@ -1,15 +0,0 @@ -HTTP/1.1 200 OK -content-type:text/html; charset=utf-8 -Transfer-Encoding: chunked -Connection: close - -4 -Wiki -5 -pedia -e - in - -chunks. -0 -SomeAfterHeader: TheData diff --git a/jodd-http/src/test/resources/jodd/http/6-response.txt b/jodd-http/src/test/resources/jodd/http/6-response.txt deleted file mode 100644 index c9077d96f..000000000 --- a/jodd-http/src/test/resources/jodd/http/6-response.txt +++ /dev/null @@ -1,16 +0,0 @@ -HTTP/1.1 200 -content-type:text/html; charset=utf-8 -Content-Length: 102 -Transfer-Encoding: chunked -Connection: close - -4 -Wiki -5 -pedia -e - in - -chunks. -0 -SomeAfterHeader: TheData diff --git a/jodd-http/src/test/resources/jodd/http/answer.json b/jodd-http/src/test/resources/jodd/http/answer.json deleted file mode 100644 index 40057d576..000000000 --- a/jodd-http/src/test/resources/jodd/http/answer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Code": "rne", - "Question": { - "Choices": [ - { - "FullName": "Abc Def", - "Code": "rfr" - }, - { - "FullName": "Crocodil Dunddy", - "Code": "mios" - }, - { - "FullName": "Super Duper", - "Code": "rne" - } - ], - "Solution": "nYZmfwV/lZKk2K7Aai30qOtNhQk=", - "UserThumb": "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" - } -} \ No newline at end of file diff --git a/jodd-http/src/test/resources/jodd/http/web.xml b/jodd-http/src/test/resources/jodd/http/web.xml deleted file mode 100644 index 2d5d23f52..000000000 --- a/jodd-http/src/test/resources/jodd/http/web.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - EchoServlet - EchoServlet - jodd.http.fixture.EchoServlet - - - - EchoServlet - /echo - - - - Echo2Servlet - Echo2Servlet - jodd.http.fixture.Echo2Servlet - - - - Echo2Servlet - /echo2 - - - - Echo3Servlet - Echo3Servlet - jodd.http.fixture.Echo3Servlet - - - - Echo3Servlet - /echo3 - - - - TargetServlet - TargetServlet - jodd.http.fixture.TargetServlet - - - - TargetServlet - /target - - - - RedirectServlet - RedirectServlet - jodd.http.fixture.RedirectServlet - - - - RedirectServlet - /redirect - - - - SlowServlet - SlowServlet - jodd.http.fixture.SlowServlet - - - - SlowServlet - /slow - - - \ No newline at end of file diff --git a/jodd-joy/build.gradle b/jodd-joy/build.gradle index 57e9728ff..152ad8899 100644 --- a/jodd-joy/build.gradle +++ b/jodd-joy/build.gradle @@ -4,7 +4,7 @@ ext.moduleDescription = 'Jodd Joy is set of Jodd extensions that makes developme dependencies { api project(':jodd-core') - api project(':jodd-props') + api project(':jodd-petite') api project(':jodd-madvoc') api project(':jodd-vtor') @@ -13,17 +13,17 @@ dependencies { api project(':jodd-proxetta') api 'org.slf4j:slf4j-api:1.7.30' api project(':jodd-decora') - api project(':jodd-http') api project(':jodd-servlet') - api 'org.jodd:jodd-mail:6.0.1' + api 'org.jodd:jodd-mail:6.0.2' + api 'org.jodd:jodd-props:6.0.1' + api 'org.jodd:jodd-http:6.0.2' api 'org.mindrot:jbcrypt:0.4' provided lib.servlet provided lib.jsp - testImplementation project(':jodd-http') // testCompile project(':jodd-db').sourceSets.test.output testImplementation lib.junit5 testImplementation lib.hsqldb diff --git a/jodd-joy/src/main/java/jodd/joy/JoyProps.java b/jodd-joy/src/main/java/jodd/joy/JoyProps.java index 5c68f4080..cea53a37c 100644 --- a/jodd-joy/src/main/java/jodd/joy/JoyProps.java +++ b/jodd-joy/src/main/java/jodd/joy/JoyProps.java @@ -25,8 +25,13 @@ package jodd.joy; +import jodd.core.JoddCore; +import jodd.exception.UncheckedException; +import jodd.io.findfile.ClassScanner; import jodd.props.Props; +import jodd.util.StringUtil; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,8 +60,8 @@ public Props getProps() { // ---------------------------------------------------------------- config - private List propsNamePatterns = new ArrayList<>(); - private List propsProfiles = new ArrayList<>(); + private final List propsNamePatterns = new ArrayList<>(); + private final List propsProfiles = new ArrayList<>(); /** * Adds props files or patterns. @@ -115,7 +120,7 @@ public void start() { final long startTime = System.currentTimeMillis(); - props.loadFromClasspath(patterns); + loadFromClasspath(props, patterns); log.debug("Props scanning completed in " + (System.currentTimeMillis() - startTime) + "ms."); @@ -124,6 +129,27 @@ public void start() { log.info("PROPS OK!"); } + private void loadFromClasspath(final Props props, final String... patterns) { + ClassScanner.create() + .registerEntryConsumer(entryData -> { + String usedEncoding = JoddCore.encoding; + if (StringUtil.endsWithIgnoreCase(entryData.name(), ".properties")) { + usedEncoding = StandardCharsets.ISO_8859_1.name(); + } + + final String encoding = usedEncoding; + UncheckedException.runAndWrapException(() -> props.load(entryData.openInputStream(), encoding)); + }) + .includeResources(true) + .ignoreException(true) + .excludeCommonJars() + .excludeAllEntries(true) + .includeEntries(patterns) + .scanDefaultClasspath() + .start(); + } + + /** * Creates new {@link Props} with default configuration. * Empty props will be ignored, and missing macros will be diff --git a/jodd-madvoc/build.gradle b/jodd-madvoc/build.gradle index db391d104..3aa9c9337 100644 --- a/jodd-madvoc/build.gradle +++ b/jodd-madvoc/build.gradle @@ -4,17 +4,17 @@ ext.moduleDescription = 'Jodd Madvoc is elegant web MVC framework that uses CoC dependencies { api project(':jodd-core') - api project(':jodd-props') api project(':jodd-servlet') api project(':jodd-petite') api project(':jodd-proxetta') - api 'org.jodd:jodd-json:6.0.1' + api 'org.jodd:jodd-json:6.0.2' + api 'org.jodd:jodd-props:6.0.1' api 'org.slf4j:slf4j-api:1.7.30' provided lib.servlet provided lib.jsp - testImplementation project(':jodd-http') + testImplementation 'org.jodd:jodd-http:6.0.2' testImplementation project(':jodd-core').sourceSets.test.output testImplementation lib.junit5 testImplementation lib.tomcat_embed diff --git a/jodd-madvoc/src/main/java/jodd/madvoc/Madvoc.java b/jodd-madvoc/src/main/java/jodd/madvoc/Madvoc.java index a0c275d38..1b83bc41e 100644 --- a/jodd-madvoc/src/main/java/jodd/madvoc/Madvoc.java +++ b/jodd-madvoc/src/main/java/jodd/madvoc/Madvoc.java @@ -25,14 +25,19 @@ package jodd.madvoc; +import jodd.core.JoddCore; +import jodd.exception.UncheckedException; +import jodd.io.findfile.ClassScanner; import jodd.props.Props; import jodd.typeconverter.Converter; import jodd.util.ClassLoaderUtil; import jodd.util.ClassUtil; +import jodd.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletContext; +import java.nio.charset.StandardCharsets; /** * Maintain the lifecycle of a Madvoc {@link WebApp}. @@ -245,13 +250,36 @@ protected Props loadMadvocParams(final String[] patterns) { log.info("Loading Madvoc parameters from: " + Converter.get().toString(patterns)); } try { - return new Props().loadFromClasspath(patterns); + final Props props = new Props(); + loadFromClasspath(props, patterns); + return props; } catch (final Exception ex) { throw new MadvocException("Unable to load Madvoc parameters from: " + Converter.get().toString(patterns) + ".properties': " + ex.toString(), ex); } } + private void loadFromClasspath(final Props props, final String... patterns) { + ClassScanner.create() + .registerEntryConsumer(entryData -> { + String usedEncoding = JoddCore.encoding; + if (StringUtil.endsWithIgnoreCase(entryData.name(), ".properties")) { + usedEncoding = StandardCharsets.ISO_8859_1.name(); + } + + final String encoding = usedEncoding; + UncheckedException.runAndWrapException(() -> props.load(entryData.openInputStream(), encoding)); + }) + .includeResources(true) + .ignoreException(true) + .excludeCommonJars() + .excludeAllEntries(true) + .includeEntries(patterns) + .scanDefaultClasspath() + .start(); + } + + /** * Loads Madvoc component that will be used for configuring the user actions. diff --git a/jodd-madvoc/src/main/java/jodd/madvoc/component/FileUploader.java b/jodd-madvoc/src/main/java/jodd/madvoc/component/FileUploader.java index cdb1a5d8a..cc823790a 100644 --- a/jodd-madvoc/src/main/java/jodd/madvoc/component/FileUploader.java +++ b/jodd-madvoc/src/main/java/jodd/madvoc/component/FileUploader.java @@ -25,8 +25,9 @@ package jodd.madvoc.component; -import jodd.io.upload.FileUploadFactory; -import jodd.io.upload.impl.AdaptiveFileUploadFactory; + +import jodd.http.upload.FileUploadFactory; +import jodd.http.upload.impl.AdaptiveFileUploadFactory; import java.util.function.Supplier; diff --git a/jodd-madvoc/src/main/java/jodd/madvoc/scope/RequestScope.java b/jodd-madvoc/src/main/java/jodd/madvoc/scope/RequestScope.java index bd69f05e3..4b660eeac 100644 --- a/jodd-madvoc/src/main/java/jodd/madvoc/scope/RequestScope.java +++ b/jodd-madvoc/src/main/java/jodd/madvoc/scope/RequestScope.java @@ -25,7 +25,7 @@ package jodd.madvoc.scope; -import jodd.io.upload.FileUpload; +import jodd.http.upload.FileUpload; import jodd.madvoc.ActionRequest; import jodd.madvoc.component.MadvocEncoding; import jodd.madvoc.config.Targets; diff --git a/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UploadAction.java b/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UploadAction.java index 08d80d865..f18ebf4ea 100644 --- a/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UploadAction.java +++ b/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UploadAction.java @@ -25,7 +25,7 @@ package jodd.madvoc.action.mv; -import jodd.io.upload.FileUpload; +import jodd.http.upload.FileUpload; import jodd.madvoc.meta.Action; import jodd.madvoc.meta.In; import jodd.madvoc.meta.MadvocAction; @@ -45,4 +45,4 @@ public String execute() { return "move:/mv/user.importList.html" ; } -} \ No newline at end of file +} diff --git a/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UserAction.java b/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UserAction.java index 9817ef5d3..6389a0642 100644 --- a/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UserAction.java +++ b/jodd-madvoc/src/test/java/jodd/madvoc/action/mv/UserAction.java @@ -25,7 +25,7 @@ package jodd.madvoc.action.mv; -import jodd.io.upload.FileUpload; +import jodd.http.upload.FileUpload; import jodd.madvoc.meta.Action; import jodd.madvoc.meta.In; import jodd.madvoc.meta.MadvocAction; @@ -48,18 +48,18 @@ public class UserAction { public String importList() throws IOException { stuff = ""; - for (FileUpload uploadFile : uploadFiles) { + for (final FileUpload uploadFile : uploadFiles) { stuff += uploadFile.getFileContent().length; stuff += uploadFile.getSize(); stuff += uploadFile.getHeader().getFileName(); stuff += " "; } - for (String uploadFileName : uploadFileNames) { + for (final String uploadFileName : uploadFileNames) { stuff += uploadFileName; stuff += " "; } return "ok"; } -} \ No newline at end of file +} diff --git a/jodd-petite/build.gradle b/jodd-petite/build.gradle index ec0353d1f..b0dfd5e08 100644 --- a/jodd-petite/build.gradle +++ b/jodd-petite/build.gradle @@ -4,11 +4,11 @@ ext.moduleDescription = 'Jodd Petite is slick and lightweight DI container that dependencies { api project(':jodd-core') - api project(':jodd-props') api project(':jodd-proxetta') api project(':jodd-servlet'), optional api 'org.slf4j:slf4j-api:1.7.30' + api 'org.jodd:jodd-props:6.0.1' provided lib.servlet, optional diff --git a/jodd-props b/jodd-props new file mode 160000 index 000000000..7f34caeb0 --- /dev/null +++ b/jodd-props @@ -0,0 +1 @@ +Subproject commit 7f34caeb0bb2573da01edf5193813484c6729c0c diff --git a/jodd-props/build.gradle b/jodd-props/build.gradle deleted file mode 100644 index 45c9ce667..000000000 --- a/jodd-props/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ - -ext.moduleName = 'Jodd Props' -ext.moduleDescription = 'Jodd Props is super properties replacement, featuring: UTF8, sections, profiles, macros and more.' - -dependencies { - api project(':jodd-core') - - testImplementation project(':jodd-core').sourceSets.test.output - testImplementation lib.junit5 -} diff --git a/jodd-props/src/main/java/jodd/props/PropertiesToProps.java b/jodd-props/src/main/java/jodd/props/PropertiesToProps.java deleted file mode 100644 index 5abf24944..000000000 --- a/jodd-props/src/main/java/jodd/props/PropertiesToProps.java +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.Map; -import java.util.Properties; - -/** - * Converter for Java Properties to Jodd Props format. - */ -class PropertiesToProps { - - /** - * Convert Java Properties to Jodd Props format - * - * @param writer Writer to write Props formatted file content to - * @param properties Properties to convert to Props format - * @param profiles Properties per profile to convert and add to the Props format - * @throws IOException On any I/O error when writing to the writer - */ - void convertToWriter(final Writer writer, final Properties properties, final Map profiles) - throws IOException { - final BufferedWriter bw = getBufferedWriter(writer); - writeBaseAndProfileProperties(bw, properties, profiles); - writeProfilePropertiesThatAreNotInTheBase(bw, properties, profiles); - bw.flush(); - } - - private void writeProfilePropertiesThatAreNotInTheBase(final BufferedWriter bw, final Properties baseProperties, - final Map profiles) throws IOException { - - for (final Map.Entry entry : profiles.entrySet()) { - final String profileName = entry.getKey(); - final Properties profileProperties = entry.getValue(); - - for (final Object key : profileProperties.keySet()) { - if (baseProperties.containsKey(key)) { - continue; - } - - final String keyString = key.toString(); - writeProfileProperty(bw, profileName, keyString, profileProperties.getProperty(keyString)); - } - } - } - - private BufferedWriter getBufferedWriter(final Writer writer) { - final BufferedWriter bw; - if (writer instanceof BufferedWriter) { - bw = (BufferedWriter) writer; - } else { - bw = new BufferedWriter(writer); - } - return bw; - } - - private void writeBaseAndProfileProperties(final BufferedWriter bw, final Properties baseProperties, - final Map profiles) throws IOException { - for (final Object key : baseProperties.keySet()) { - final String keyString = key.toString(); - - final String value = baseProperties.getProperty(keyString); - writeBaseProperty(bw, keyString, value); - writeProfilePropertiesOfKey(bw, keyString, profiles); - } - } - - private void writeProfilePropertiesOfKey(final BufferedWriter bw, final String key, - final Map profiles) throws IOException { - for (final Map.Entry entry : profiles.entrySet()) { - final Properties profileProperties = entry.getValue(); - if (!profileProperties.containsKey(key)) { - continue; - } - final String profileName = entry.getKey(); - writeProfileProperty(bw, profileName, key, profileProperties.getProperty(key)); - } - } - - private void writeProfileProperty(final BufferedWriter bw, final String profileName, - final String key, final String value) - throws IOException { - bw.write(key + '<' + profileName + '>' + '=' + value); - bw.newLine(); - } - - private void writeBaseProperty(final BufferedWriter bw, final String key, final String value) - throws IOException { - bw.write(key + '=' + value); - bw.newLine(); - } -} \ No newline at end of file diff --git a/jodd-props/src/main/java/jodd/props/Props.java b/jodd-props/src/main/java/jodd/props/Props.java deleted file mode 100644 index c3d4c1ca1..000000000 --- a/jodd-props/src/main/java/jodd/props/Props.java +++ /dev/null @@ -1,754 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.core.JoddCore; -import jodd.exception.UncheckedException; -import jodd.io.FastCharArrayWriter; -import jodd.io.FileNameUtil; -import jodd.io.FileUtil; -import jodd.io.IOUtil; -import jodd.io.findfile.ClassScanner; -import jodd.util.StringPool; -import jodd.util.StringUtil; -import jodd.util.Wildcard; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; - - -/** - * Super properties: fast, configurable, supports (ini) sections, profiles. - *

- * Basic parsing rules: - *

    - *
  • By default, props files are UTF8 encoded. - *
  • Leading and trailing spaces will be trimmed from section names and property names. - *
  • Leading and/or trailing spaces may be trimmed from property values. - *
  • You can use either equal sign (=) or colon (:) to assign property values - *
  • Comments begin with either a semicolon (;), or a sharp sign (#) and extend to the end of line. It doesn't have to be the first character. - *
  • A backslash (\) escapes the next character (e.g., \# is a literal #, \\ is a literal \). - *
  • If the last character of a line is backslash (\), the value is continued on the next line with new line character included. - *
  • \\uXXXX is encoded as character - *
  • \t, \r and \f are encoded as characters - *
- *

- * Sections rules: - *

    - *
  • Section names are enclosed between [ and ]. - *
  • Properties following a section header belong to that section. Section name is added as a prefix to section properties. - *
  • Section ends with empty section definition [] or with new section start - *
- *

- * Profiles rules: - *

    - *
  • Profile names are enclosed between < and > in property key. - *
  • Each property key may contain zero, one or more profile definitions. - *
- *

- * Macro rules: - *

    - *
  • Profile values may contain references to other properties using ${ and } - *
  • Inner references are supported - *
  • References are resolved first in the profile context and then in the base props context. - *
- */ -public class Props implements Cloneable { - - private static final String DEFAULT_PROFILES_PROP = "@profiles"; - - protected final PropsParser parser; - - protected final PropsData data; - - protected String activeProfilesProp = DEFAULT_PROFILES_PROP; - - protected String[] activeProfiles; - - protected volatile boolean initialized; - - /** - * Statis ctor. - */ - public static Props create() { - return new Props(); - } - - - /** - * Creates new props. - */ - public Props() { - this(new PropsParser()); - } - - protected Props(final PropsParser parser) { - this.parser = parser; - this.data = parser.getPropsData(); - } - - /** - * Clones props by creating new instance and copying current configuration. - */ - @Override - protected Props clone() { - final PropsParser parser = this.parser.clone(); - final Props p = new Props(parser); - - p.activeProfilesProp = activeProfilesProp; - return p; - } - - /** - * Returns active profiles or null if none defined. - */ - public String[] getActiveProfiles() { - initialize(); - return activeProfiles; - } - - // ---------------------------------------------------------------- configuration - - /** - * Sets new active profiles and overrides existing ones. - * By setting null, no active profile will be set. - *

- * Note that if some props are loaded after - * this method call, they might override active profiles - * by using special property for active profiles (@profiles). - */ - public Props setActiveProfiles(final String... activeProfiles) { - initialized = false; - this.activeProfiles = activeProfiles; - return this; - } - - /** - * Specifies the new line string when EOL is escaped. - * Default value is an empty string. - */ - public Props setEscapeNewLineValue(final String escapeNewLineValue) { - parser.escapeNewLineValue = escapeNewLineValue; - return this; - } - - /** - * Specifies should the values be trimmed from the left. - * Default is true. - */ - public Props setValueTrimLeft(final boolean valueTrimLeft) { - parser.valueTrimLeft = valueTrimLeft; - return this; - } - - /** - * Specifies should the values be trimmed from the right. - * Default is true. - */ - public Props setValueTrimRight(final boolean valueTrimRight) { - parser.valueTrimRight = valueTrimRight; - return this; - } - - /** - * Defines if the prefix whitespaces should be ignored when value is split into the lines. - */ - public Props setIgnorePrefixWhitespacesOnNewLine(final boolean ignorePrefixWhitespacesOnNewLine) { - parser.ignorePrefixWhitespacesOnNewLine = ignorePrefixWhitespacesOnNewLine; - return this; - } - - /** - * Skips empty properties as they don't exist. - */ - public Props setSkipEmptyProps(final boolean skipEmptyProps) { - parser.skipEmptyProps = skipEmptyProps; - data.skipEmptyProps = skipEmptyProps; - return this; - } - - /** - * Appends duplicate props. - */ - public Props setAppendDuplicateProps(final boolean appendDuplicateProps) { - data.appendDuplicateProps = appendDuplicateProps; - return this; - } - - /** - * Ignore missing macros by replacing them with an empty string. - */ - public Props setIgnoreMissingMacros(final boolean ignoreMissingMacros) { - data.ignoreMissingMacros = ignoreMissingMacros; - return this; - } - - /** - * Enables multiline values. - */ - public Props setMultilineValues(final boolean multilineValues) { - parser.multilineValues = multilineValues; - return this; - } - - /** - * Parses input string and loads provided properties map. - */ - protected synchronized void parse(final String data) { - initialized = false; - parser.parse(data); - } - - // ---------------------------------------------------------------- load - - /** - * Loads props from the string. - */ - public Props load(final String data) { - parse(data); - return this; - } - - /** - * Loads props from the file. Assumes UTF8 encoding unless - * the file ends with '.properties', than it uses ISO 8859-1. - */ - public Props load(final File file) throws IOException { - final String extension = FileNameUtil.getExtension(file.getAbsolutePath()); - final String data; - if (extension.equalsIgnoreCase("properties")) { - data = FileUtil.readString(file, StandardCharsets.ISO_8859_1); - } else { - data = FileUtil.readString(file); - } - parse(data); - return this; - } - - /** - * Loads properties from the file in provided encoding. - */ - public Props load(final File file, final String encoding) throws IOException { - parse(FileUtil.readString(file, Charset.forName(encoding))); - return this; - } - - /** - * Loads properties from input stream. Stream is not closed at the end. - */ - public Props load(final InputStream in) throws IOException { - final Writer out = new FastCharArrayWriter(); - IOUtil.copy(in, out); - parse(out.toString()); - return this; - } - - /** - * Loads properties from input stream and provided encoding. - * Stream is not closed at the end. - */ - public Props load(final InputStream in, final String encoding) throws IOException { - final Writer out = new FastCharArrayWriter(); - IOUtil.copy(in, out, Charset.forName(encoding)); - parse(out.toString()); - return this; - } - - /** - * Loads base properties from the provided java properties. - * Null values are ignored. - */ - public Props load(final Map p) { - for (final Map.Entry entry : p.entrySet()) { - final String name = entry.getKey().toString(); - final Object value = entry.getValue(); - if (value == null) { - continue; - } - data.putBaseProperty(name, value.toString(), false); - } - return this; - } - - /** - * Loads base properties from java Map using provided prefix. - * Null values are ignored. - */ - @SuppressWarnings("unchecked") - public Props load(final Map map, final String prefix) { - String realPrefix = prefix; - realPrefix += '.'; - for (final Map.Entry entry : map.entrySet()) { - final String name = entry.getKey().toString(); - final Object value = entry.getValue(); - if (value == null) { - continue; - } - data.putBaseProperty(realPrefix + name, value.toString(), false); - } - return this; - } - - /** - * Loads system properties with given prefix. - * If prefix is null it will not be ignored. - */ - public Props loadSystemProperties(final String prefix) { - final Properties environmentProperties = System.getProperties(); - load(environmentProperties, prefix); - return this; - } - - /** - * Loads environment properties with given prefix. - * If prefix is null it will not be used. - */ - public Props loadEnvironment(final String prefix) { - final Map environmentMap = System.getenv(); - load(environmentMap, prefix); - return this; - } - - /** - * Loads props and properties from the classpath. - */ - public Props loadFromClasspath(final String... patterns) { - ClassScanner.create() - .registerEntryConsumer(entryData -> { - String usedEncoding = JoddCore.encoding; - if (StringUtil.endsWithIgnoreCase(entryData.name(), ".properties")) { - usedEncoding = StandardCharsets.ISO_8859_1.name(); - } - - final String encoding = usedEncoding; - UncheckedException.runAndWrapException(() -> load(entryData.openInputStream(), encoding)); - }) - .includeResources(true) - .ignoreException(true) - .excludeCommonJars() - .excludeAllEntries(true) - .includeEntries(patterns) - .scanDefaultClasspath() - .start(); - return this; - } - - - // ---------------------------------------------------------------- props - - /** - * Counts the total number of properties, including all profiles. - * This operation performs calculation each time and it might be - * more time consuming then expected. - */ - public int countTotalProperties() { - return data.countBaseProperties() + data.countProfileProperties(); - } - - /** - * Returns string value of base property. - * Returns null if property doesn't exist. - */ - @SuppressWarnings({"NullArgumentToVariableArgMethod"}) - public String getBaseValue(final String key) { - return getValue(key, StringPool.EMPTY_ARRAY); - } - - /** - * Returns value of property, using active profiles, or {@code null} if property not found. - */ - public String getValue(final String key) { - initialize(); - return data.lookupValue(key, activeProfiles); - } - - /** - * Returns value of property, using active profiles or default value if not found. - */ - public String getValueOrDefault(final String key, final String defaultValue) { - initialize(); - final String value = data.lookupValue(key, activeProfiles); - if (value == null) { - return defaultValue; - } - return value; - } - - /** - * Returns integer value of given property or {@code null} if property not found. - */ - public Integer getIntegerValue(final String key) { - final String value = getValue(key); - if (value == null) { - return null; - } - return Integer.valueOf(value); - } - - /** - * Returns integer value or default one if property not defined. - */ - public Integer getIntegerValue(final String key, final Integer defaultValue) { - final String value = getValue(key); - if (value == null) { - return defaultValue; - } - return Integer.valueOf(value); - } - - /** - * Returns long value of given property or {@code null} if property not found. - */ - public Long getLongValue(final String key) { - final String value = getValue(key); - if (value == null) { - return null; - } - return Long.valueOf(value); - } - - /** - * Returns long value or default one if property not defined. - */ - public Long getLongValue(final String key, final Long defaultValue) { - final String value = getValue(key); - if (value == null) { - return defaultValue; - } - return Long.valueOf(value); - } - - /** - * Returns double value of given property or {@code null} if property not found. - */ - public Double getDoubleValue(final String key) { - final String value = getValue(key); - if (value == null) { - return null; - } - return Double.valueOf(value); - } - - /** - * Returns double value or default one if property not defined. - */ - public Double getDoubleValue(final String key, final Double defaultValue) { - final String value = getValue(key); - if (value == null) { - return defaultValue; - } - return Double.valueOf(value); - } - - /** - * Returns boolean value of given property or {@code null} if property not found. - */ - public Boolean getBooleanValue(final String key) { - final String value = getValue(key); - if (value == null) { - return null; - } - return Boolean.valueOf(value); - } - - /** - * Returns boolean value or default one if property not defined. - */ - public Boolean getBooleanValue(final String key, final Boolean defaultValue) { - final String value = getValue(key); - if (value == null) { - return defaultValue; - } - return Boolean.valueOf(value); - } - - /** - * Returns string value of given profiles. If key is not - * found under listed profiles, base properties will be searched. - * Returns null if property doesn't exist. - */ - public String getValue(final String key, final String... profiles) { - initialize(); - return data.lookupValue(key, profiles); - } - - public Integer getIntegerValue(final String key, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return null; - } - return Integer.valueOf(value); - } - public Integer getIntegerValue(final String key, final Integer defaultValue, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return defaultValue; - } - return Integer.valueOf(value); - } - public Long getLongValue(final String key, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return null; - } - return Long.valueOf(value); - } - public Long getLongValue(final String key, final Long defaultValue, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return defaultValue; - } - return Long.valueOf(value); - } - public Double getDoubleValue(final String key, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return null; - } - return Double.valueOf(value); - } - public Double getDoubleValue(final String key, final Double defaultValue, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return defaultValue; - } - return Double.valueOf(value); - } - public Boolean getBooleanValue(final String key, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return null; - } - return Boolean.valueOf(value); - } - public Boolean getBooleanValue(final String key, final Boolean defaultValue, final String... profiles) { - final String value = getValue(key, profiles); - if (value == null) { - return defaultValue; - } - return Boolean.valueOf(value); - } - - - /** - * Sets default value. - */ - public void setValue(final String key, final String value) { - setValue(key, value, null); - } - - /** - * Sets value on some profile. - */ - public void setValue(final String key, final String value, final String profile) { - if (profile == null) { - data.putBaseProperty(key, value, false); - } else { - data.putProfileProperty(key, value, profile, false); - } - initialized = false; - } - - // ---------------------------------------------------------------- extract - - /** - * Extracts props belonging to active profiles. - */ - public void extractProps(final Map target) { - initialize(); - data.extract(target, activeProfiles, null, null); - } - - /** - * Extract props of given profiles. - */ - public void extractProps(final Map target, final String... profiles) { - initialize(); - data.extract(target, profiles, null, null); - } - - /** - * Extracts subset of properties that matches given wildcards. - */ - public void extractSubProps(final Map target, final String... wildcardPatterns) { - initialize(); - data.extract(target, activeProfiles, wildcardPatterns, null); - } - - /** - * Extracts subset of properties that matches given wildcards. - */ - public void extractSubProps(final Map target, final String[] profiles, final String[] wildcardPatterns) { - initialize(); - data.extract(target, profiles, wildcardPatterns, null); - } - - // ---------------------------------------------------------------- childMap - - /** - * Returns inner map from the props with given prefix. Keys in returned map - * will not have the prefix. - */ - @SuppressWarnings("unchecked") - public Map innerMap(final String prefix) { - initialize(); - return data.extract(null, activeProfiles, null, prefix); - } - - /** - * Adds child map to the props on given prefix. - */ - public void addInnerMap(final String prefix, final Map map) { - addInnerMap(prefix, map, null); - } - - /** - * Adds child map to the props on given prefix. - */ - public void addInnerMap(String prefix, final Map map, final String profile) { - if (!StringUtil.endsWithChar(prefix, '.')) { - prefix += StringPool.DOT; - } - - for (final Map.Entry entry : map.entrySet()) { - String key = entry.getKey().toString(); - - key = prefix + key; - - setValue(key, entry.getValue().toString(), profile); - } - } - - // ---------------------------------------------------------------- initialize - - /** - * Initializes props. By default it only resolves active profiles. - */ - protected void initialize() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - - resolveActiveProfiles(); - - initialized = true; - } - } - } - } - - /** - * Resolves active profiles from special property. - * This property can be only a base property! - * If default active property is not defined, nothing happens. - * Otherwise, it will replace currently active profiles. - */ - protected void resolveActiveProfiles() { - if (activeProfilesProp == null) { - activeProfiles = null; - return; - } - - final PropsEntry pv = data.getBaseProperty(activeProfilesProp); - if (pv == null) { - // no active profile set as the property, exit - return; - } - - final String value = pv.getValue(); - if (StringUtil.isBlank(value)) { - activeProfiles = null; - return; - } - - activeProfiles = StringUtil.splitc(value, ','); - StringUtil.trimAll(activeProfiles); - } - - // ---------------------------------------------------------------- iterator - - /** - * Returns all profiles names. - */ - public String[] getAllProfiles() { - final String[] profiles = new String[data.profileProperties.size()]; - - int index = 0; - for (final String profileName : data.profileProperties.keySet()) { - profiles[index] = profileName; - index++; - } - return profiles; - } - - /** - * Returns all the profiles that define certain prop's key name. - * Key name is given as a wildcard, or it can be matched fully. - */ - public String[] getProfilesFor(final String propKeyNameWildcard) { - final HashSet profiles = new HashSet<>(); - - profile: - for (final Map.Entry> entries : data.profileProperties.entrySet()) { - final String profileName = entries.getKey(); - - final Map value = entries.getValue(); - - for (final String propKeyName : value.keySet()) { - if (Wildcard.equalsOrMatch(propKeyName, propKeyNameWildcard)) { - profiles.add(profileName); - continue profile; - } - } - } - - return profiles.toArray(new String[0]); - } - - /** - * Returns {@link PropsEntries builder} for entries {@link #iterator() itertor}. - */ - public PropsEntries entries() { - initialize(); - return new PropsEntries(this); - } - - /** - * Returns iterator for active profiles. - */ - public Iterator iterator() { - return entries().activeProfiles().iterator(); - } - -} diff --git a/jodd-props/src/main/java/jodd/props/PropsConverter.java b/jodd-props/src/main/java/jodd/props/PropsConverter.java deleted file mode 100644 index 769ec8edd..000000000 --- a/jodd-props/src/main/java/jodd/props/PropsConverter.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.Map; -import java.util.Properties; - -/** - * Converter of Java Properties to Jodd Props format. - */ -public class PropsConverter { - - /** - * Convert Java Properties to Jodd Props format. - * - * @param writer Writer to write Props formatted file content to - * @param properties Properties to convert to Props format - * @throws IOException On any I/O error when writing to the writer - */ - public static void convert(final Writer writer, final Properties properties) throws IOException { - convert(writer, properties, Collections.emptyMap()); - } - - /** - * Convert Java Properties to Jodd Props format. - * - * @param writer Writer to write Props formatted file content to - * @param properties Properties to convert to Props format - * @param profiles Properties per profile to convert and add to the Props format - * @throws IOException On any I/O error when writing to the writer - */ - public static void convert(final Writer writer, final Properties properties, final Map profiles) - throws IOException { - - final PropertiesToProps toProps = new PropertiesToProps(); - toProps.convertToWriter(writer, properties, profiles); - } - -} diff --git a/jodd-props/src/main/java/jodd/props/PropsData.java b/jodd-props/src/main/java/jodd/props/PropsData.java deleted file mode 100644 index 0385e90bf..000000000 --- a/jodd-props/src/main/java/jodd/props/PropsData.java +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.util.StringPool; -import jodd.util.StringTemplateParser; -import jodd.util.StringUtil; -import jodd.util.Wildcard; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.function.Function; - -/** - * Props data storage for base and profile properties. - * Properties can be lookuped and modified only through this - * class. - */ -public class PropsData implements Cloneable { - - private static final int MAX_INNER_MACROS = 100; - private static final String APPEND_SEPARATOR = ","; - - protected final HashMap baseProperties; - protected final HashMap> profileProperties; - - protected PropsEntry first; - protected PropsEntry last; - - /** - * If set, duplicate props will be appended to the end, separated by comma. - */ - protected boolean appendDuplicateProps; - - /** - * When set, missing macros will be replaces with an empty string. - */ - protected boolean ignoreMissingMacros; - - /** - * When set, empty properties will be skipped. - */ - protected boolean skipEmptyProps = true; - - public PropsData() { - this(new HashMap<>(), new HashMap<>()); - } - - protected PropsData(final HashMap properties, final HashMap> profiles) { - this.baseProperties = properties; - this.profileProperties = profiles; - } - - @Override - public PropsData clone() { - final HashMap> newProfiles = new HashMap<>(); - - final HashMap newBase = new HashMap<>(baseProperties); - - for (final Map.Entry> entry : profileProperties.entrySet()) { - final Map map = new HashMap<>(entry.getValue().size()); - map.putAll(entry.getValue()); - newProfiles.put(entry.getKey(), map); - } - - final PropsData pd = new PropsData(newBase, newProfiles); - pd.appendDuplicateProps = appendDuplicateProps; - pd.ignoreMissingMacros = ignoreMissingMacros; - pd.skipEmptyProps = skipEmptyProps; - return pd; - } - - // ---------------------------------------------------------------- misc - - /** - * Puts key-value pair into the map, with respect of appending duplicate properties - */ - protected void put(final String profile, final Map map, final String key, final String value, final boolean append) { - String realValue = value; - if (append || appendDuplicateProps) { - final PropsEntry pv = map.get(key); - if (pv != null) { - realValue = pv.value + APPEND_SEPARATOR + realValue; - } - } - final PropsEntry propsEntry = new PropsEntry(key, realValue, profile, this); - - // update position pointers - if (first == null) { - first = propsEntry; - } else { - last.next = propsEntry; - } - last = propsEntry; - - // add to the map - map.put(key, propsEntry); - } - - // ---------------------------------------------------------------- properties - - /** - * Counts base properties. - */ - public int countBaseProperties() { - return baseProperties.size(); - } - - /** - * Adds base property. - */ - public void putBaseProperty(final String key, final String value, final boolean append) { - put(null, baseProperties, key, value, append); - } - - /** - * Returns base property or null if it doesn't exist. - */ - public PropsEntry getBaseProperty(final String key) { - return baseProperties.get(key); - } - - // ---------------------------------------------------------------- profiles - - /** - * Counts profile properties. Note: this method is not - * that easy on execution. - */ - public int countProfileProperties() { - final HashSet profileKeys = new HashSet<>(); - - for (final Map map : profileProperties.values()) { - for (final String key : map.keySet()) { - if (!baseProperties.containsKey(key)) { - profileKeys.add(key); - } - } - } - return profileKeys.size(); - } - - /** - * Adds profile property. - */ - public void putProfileProperty(final String key, final String value, final String profile, final boolean append) { - final Map map = profileProperties.computeIfAbsent(profile, k -> new HashMap<>()); - put(profile, map, key, value, append); - } - - /** - * Returns profile property. - */ - public PropsEntry getProfileProperty(final String profile, final String key) { - final Map profileMap = profileProperties.get(profile); - if (profileMap == null) { - return null; - } - return profileMap.get(key); - } - - - // ---------------------------------------------------------------- lookup - - /** - * Lookup props value through profiles and base properties. - * Returns {@code null} if value not found. - */ - protected String lookupValue(final String key, final String... profiles) { - if (profiles != null) { - for (String profile : profiles) { - if (profile == null) { - continue; - } - while (true) { - final Map profileMap = this.profileProperties.get(profile); - if (profileMap != null) { - final PropsEntry value = profileMap.get(key); - - if (value != null) { - return value.getValue(profiles); - } - } - - // go back with profile - final int ndx = profile.lastIndexOf('.'); - if (ndx == -1) { - break; - } - profile = profile.substring(0, ndx); - } - } - } - final PropsEntry value = getBaseProperty(key); - - if (value == null) { - return null; - } - - return value.getValue(profiles); - } - - // ---------------------------------------------------------------- resolve - - /** - * Resolves all macros in this props set. Called on property lookup. - */ - public String resolveMacros(String value, final String... profiles) { - // create string template parser that will be used internally - final Function macroResolver = macroName -> { - String[] lookupProfiles = profiles; - - final int leftIndex = macroName.indexOf('<'); - if (leftIndex != -1) { - final int rightIndex = macroName.indexOf('>'); - - final String profiles1 = macroName.substring(leftIndex + 1, rightIndex); - macroName = macroName.substring(0, leftIndex).concat(macroName.substring(rightIndex + 1)); - - lookupProfiles = StringUtil.splitc(profiles1, ','); - - StringUtil.trimAll(lookupProfiles); - } - - return lookupValue(macroName, lookupProfiles); - }; - - - final StringTemplateParser stringTemplateParser = new StringTemplateParser(macroResolver); - stringTemplateParser.setResolveEscapes(false); - - if (!ignoreMissingMacros) { - stringTemplateParser.setReplaceMissingKey(false); - } else { - stringTemplateParser.setReplaceMissingKey(true); - stringTemplateParser.setMissingKeyReplacement(StringPool.EMPTY); - } - - // start parsing - int loopCount = 0; - - while (loopCount++ < MAX_INNER_MACROS) { - final String newValue = stringTemplateParser.apply(value); - - if (newValue.equals(value)) { - break; - } - - if (skipEmptyProps) { - if (newValue.length() == 0) { - return null; - } - } - - value = newValue; - } - - return value; - } - - // ---------------------------------------------------------------- extract - - /** - * Extracts props to target map. This is all-in-one method, that does many things at once. - */ - public Map extract(Map target, final String[] profiles, final String[] wildcardPatterns, String prefix) { - if (target == null) { - target = new HashMap(); - } - - // make sure prefix ends with a dot - if (prefix != null) { - if (!StringUtil.endsWithChar(prefix, '.')) { - prefix += StringPool.DOT; - } - } - - if (profiles != null) { - for (String profile : profiles) { - while (true) { - final Map map = this.profileProperties.get(profile); - if (map != null) { - extractMap(target, map, profiles, wildcardPatterns, prefix); - } - - final int ndx = profile.lastIndexOf('.'); - if (ndx == -1) { - break; - } - profile = profile.substring(0, ndx); - } - } - } - - extractMap(target, this.baseProperties, profiles, wildcardPatterns, prefix); - - return target; - } - - @SuppressWarnings("unchecked") - protected void extractMap( - final Map target, - final Map map, - final String[] profiles, - final String[] wildcardPatterns, - final String prefix - ) { - - for (final Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - - if (wildcardPatterns != null) { - if (Wildcard.matchOne(key, wildcardPatterns) == -1) { - continue; - } - } - - // shorten the key - if (prefix != null) { - if (!key.startsWith(prefix)) { - continue; - } - key = key.substring(prefix.length()); - } - - // only append if target DOES NOT contain the key - if (!target.containsKey(key)) { - target.put(key, entry.getValue().getValue(profiles)); - } - } - } - -} diff --git a/jodd-props/src/main/java/jodd/props/PropsEntries.java b/jodd-props/src/main/java/jodd/props/PropsEntries.java deleted file mode 100644 index 66c7af086..000000000 --- a/jodd-props/src/main/java/jodd/props/PropsEntries.java +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.util.CollectionUtil; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.function.Consumer; - -/** - * Props iterator builder. Should be used with: {@link jodd.props.Props#entries()}. - */ -public final class PropsEntries { - - private final PropsIterator propsIterator; - private final Props props; - - public PropsEntries(final Props props) { - this.props = props; - this.propsIterator = new PropsIterator(); - } - - /** - * Enables profile to iterate. - */ - public PropsEntries profile(final String profile) { - addProfiles(profile); - return this; - } - /** - * Enables profiles to iterate. - */ - public PropsEntries profile(final String... profiles) { - if (profiles == null) { - return this; - } - for (final String profile : profiles) { - addProfiles(profile); - } - return this; - } - - /** - * Enables active profiles to iterate over. - */ - public PropsEntries activeProfiles() { - profile(props.activeProfiles); - return this; - } - - private void addProfiles(final String profile) { - if (propsIterator.profiles == null) { - propsIterator.profiles = new ArrayList<>(); - } - propsIterator.profiles.add(profile); - } - - /** - * Enables section to iterate. - */ - public PropsEntries section(final String section) { - addSection(section); - return this; - } - /** - * Enables sections to iterate. - */ - public PropsEntries section(final String... section) { - for (final String s : section) { - addSection(s); - } - return this; - } - - private void addSection(final String section) { - if (propsIterator.sections == null) { - propsIterator.sections = new ArrayList<>(); - } - propsIterator.sections.add(section + '.'); - } - - /** - * Skips duplicate keys (defined in different profiles) which value is not - * used for setting current key value. - */ - public PropsEntries skipDuplicatesByValue() { - propsIterator.skipDuplicatesByValue = true; - propsIterator.skipDuplicatesByPosition = false; - return this; - } - - /** - * Skips all keys after first definition, even if value is set later. - */ - public PropsEntries skipDuplicatesByPosition() { - propsIterator.skipDuplicatesByPosition = true; - propsIterator.skipDuplicatesByValue = false; - return this; - } - - /** - * Returns populated iterator. - */ - public Iterator iterator() { - return propsIterator; - } - - /** - * Consumer all properties. - */ - public void forEach(final Consumer propsDataConsumer) { - CollectionUtil.streamOf(propsIterator).forEach(propsDataConsumer); - } - - // ---------------------------------------------------------------- iterator - - /** - * Props iterator. - */ - private class PropsIterator implements Iterator { - private PropsEntry next = props.data.first; - private boolean firstTime = true; - private List profiles; - private List sections; - private boolean skipDuplicatesByValue; - private boolean skipDuplicatesByPosition; - private Set keys; - - @Override - public boolean hasNext() { - if (firstTime) { - start(); - } - return next != null; - } - - /** - * Starts with the iterator. - */ - private void start() { - firstTime = false; - - while (!accept(next)) { - if (next == null) { - break; - } - next = next.next; // funny :))) - } - } - - /** - * Accepts an entry and returns true - * if entry should appear in this iteration. - */ - private boolean accept(final PropsEntry entry) { - if (entry == null) { - return false; - } - if (profiles != null) { - if (entry.getProfile() != null) { - boolean found = false; - for (final String profile : profiles) { - if (entry.getProfile().equals(profile)) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - } else { - // ignore all profile keys if only a base profile is active - if (entry.getProfile() != null) { - return false; - } - } - - if (sections != null) { - boolean found = false; - for (final String section : sections) { - if (entry.getKey().startsWith(section)) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - - if (profiles != null) { - if (skipDuplicatesByValue) { - final String thisProfile = entry.getProfile(); - - // iterate all profiles before this one - for (final String profile : profiles) { - if (profile.equals(thisProfile)) { - // if we came to this point, there is no - // property defined in higher profile - // therefore this one is the most important - return true; - } - - // check if key exist in higher profile - final Map profileMap = props.data.profileProperties.get(profile); - if (profileMap == null) { - continue; - } - if (profileMap.containsKey(entry.getKey())) { - // duplicate key exist in higher profile, therefore this one is less important - return false; - } - } - } - if (skipDuplicatesByPosition) { - if (keys == null) { - keys = new HashSet<>(); - } - if (!keys.add(entry.getKey())) { - return false; // the key was already there - } - } - } - - return true; - } - - @Override - public PropsEntry next() { - if (firstTime) { - start(); - } - - if (next == null) { - throw new NoSuchElementException(); - } - - final PropsEntry returnValue = next; - - while (next != null) { - next = next.next; - - if (accept(next)) { - break; - } - } - - return returnValue; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - -} diff --git a/jodd-props/src/main/java/jodd/props/PropsEntry.java b/jodd-props/src/main/java/jodd/props/PropsEntry.java deleted file mode 100644 index bdc0fd651..000000000 --- a/jodd-props/src/main/java/jodd/props/PropsEntry.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -/** - * Holds props value. - */ -public class PropsEntry { - - /** - * Original value. - */ - protected final String value; - - protected PropsEntry next; - - protected final String key; - - protected final String profile; - - protected final boolean hasMacro; - - protected final PropsData propsData; - - public PropsEntry(final String key, final String value, final String profile, final PropsData propsData) { - this.value = value; - this.key = key; - this.profile = profile; - this.hasMacro = value.contains("${"); - this.propsData = propsData; - } - - /** - * Returns the raw value. Macros are not replaced. - */ - public String getValue() { - return value; - } - - /** - * Returns the property value, with replaced macros. - */ - public String getValue(final String... profiles) { - if (hasMacro) { - return propsData.resolveMacros(value, profiles); - } - return value; - } - - /** - * Returns property key. - */ - public String getKey() { - return key; - } - - /** - * Returns property profile or null if this is a base property. - */ - public String getProfile() { - return profile; - } - - /** - * Returns true if value has a macro to resolve. - */ - public boolean hasMacro() { - return hasMacro; - } - - @Override - public String toString() { - return "PropsEntry{" + key + (profile != null ? '<' + profile + '>' : "") + '=' + value + '}'; - } - -} \ No newline at end of file diff --git a/jodd-props/src/main/java/jodd/props/PropsParser.java b/jodd-props/src/main/java/jodd/props/PropsParser.java deleted file mode 100644 index f598f9466..000000000 --- a/jodd-props/src/main/java/jodd/props/PropsParser.java +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.util.CharUtil; -import jodd.util.StringPool; -import jodd.util.StringUtil; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * {@link Props} parser. - */ -public class PropsParser implements Cloneable { - - protected static final String PROFILE_LEFT = "<"; - - protected static final String PROFILE_RIGHT = ">"; - - protected final PropsData propsData; - - /** - * Value that will be inserted when escaping the new line. - */ - protected String escapeNewLineValue = StringPool.EMPTY; - - /** - * Trims left the value. - */ - protected boolean valueTrimLeft = true; - - /** - * Trims right the value. - */ - protected boolean valueTrimRight = true; - - /** - * Defines if starting whitespaces when value is split in the new line - * should be ignored or not. - */ - protected boolean ignorePrefixWhitespacesOnNewLine = true; - - /** - * Defines if multi-line values may be written using triple-quotes - * as in python. - */ - protected boolean multilineValues = true; - - /** - * Don't include empty properties. - */ - protected boolean skipEmptyProps = true; - - public PropsParser() { - this.propsData = new PropsData(); - } - - public PropsParser(final PropsData propsData) { - this.propsData = propsData; - } - - public PropsData getPropsData() { - return propsData; - } - - @Override - public PropsParser clone() { - final PropsParser pp = new PropsParser(this.propsData.clone()); - - pp.escapeNewLineValue = escapeNewLineValue; - pp.valueTrimLeft = valueTrimLeft; - pp.valueTrimRight = valueTrimRight; - pp.ignorePrefixWhitespacesOnNewLine = ignorePrefixWhitespacesOnNewLine; - pp.skipEmptyProps = skipEmptyProps; - pp.multilineValues = multilineValues; - - return pp; - } - - /** - * Parsing states. - */ - protected enum ParseState { - TEXT, - ESCAPE, - ESCAPE_NEWLINE, - COMMENT, - VALUE - } - - /** - * Different assignment operators. - */ - protected enum Operator { - ASSIGN, - QUICK_APPEND, - COPY - } - - /** - * Loads properties. - */ - public void parse(final String in) { - ParseState state = ParseState.TEXT; - ParseState stateOnEscape = null; - - boolean insideSection = false; - String currentSection = null; - String key = null; - Operator operator = Operator.ASSIGN; - final StringBuilder sb = new StringBuilder(); - - final int len = in.length(); - int ndx = 0; - while (ndx < len) { - final char c = in.charAt(ndx); - ndx++; - - if (state == ParseState.COMMENT) { - // comment, skip to the end of the line - if (c == '\r') { - if ((ndx < len) && (in.charAt(ndx) == '\n')) { - ndx++; - } - state = ParseState.TEXT; - } - else if (c == '\n') { - state = ParseState.TEXT; - } - } else if (state == ParseState.ESCAPE) { - state = stateOnEscape; //ParseState.VALUE; - switch (c) { - case '\r': - if ((ndx < len) && (in.charAt(ndx) == '\n')) { - ndx++; - } - case '\n': - // need to go 1 step back in order to escape - // the current line ending in the follow-up state - ndx--; - state = ParseState.ESCAPE_NEWLINE; - break; - // encode UTF character - case 'u': - int value = 0; - - for (int i = 0; i < 4; i++) { - final char hexChar = in.charAt(ndx++); - if (CharUtil.isDigit(hexChar)) { - value = (value << 4) + hexChar - '0'; - } else if (hexChar >= 'a' && hexChar <= 'f') { - value = (value << 4) + 10 + hexChar - 'a'; - } else if (hexChar >= 'A' && hexChar <= 'F') { - value = (value << 4) + 10 + hexChar - 'A'; - } else { - throw new IllegalArgumentException("Malformed \\uXXXX encoding."); - } - } - sb.append((char) value); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'r': - sb.append('\r'); - break; - case 'f': - sb.append('\f'); - break; - default: - sb.append(c); - } - } else if (state == ParseState.TEXT) { - switch (c) { - case '\\': - // escape char, take the next char as is - stateOnEscape = state; - state = ParseState.ESCAPE; - break; - - // start section - case '[': - if (sb.length() > 0) { - if (StringUtil.isNotBlank(sb)) { - sb.append(c); - // previous string is not blank, hence it's not the section - break; - } - } - sb.setLength(0); - insideSection = true; - break; - - // end section - case ']': - if (insideSection) { - currentSection = sb.toString().trim(); - sb.setLength(0); - insideSection = false; - if (currentSection.length() == 0) { - currentSection = null; - } - } else { - sb.append(c); - } - break; - - case '#': - case ';': - state = ParseState.COMMENT; - break; - - // copy operator - case '<': - if (ndx == len || in.charAt(ndx) != '=') { - sb.append(c); - break; - } - operator = Operator.COPY; - //ndx++; - continue; - - // assignment operator - case '+': - if (ndx == len || in.charAt(ndx) != '=') { - sb.append(c); - break; - } - operator = Operator.QUICK_APPEND; - //ndx++; - continue; - case '=': - case ':': - if (key == null) { - key = sb.toString().trim(); - sb.setLength(0); - } else { - sb.append(c); - } - state = ParseState.VALUE; - break; - - case '\r': - case '\n': - add(currentSection, key, sb, true, operator); - sb.setLength(0); - key = null; - operator = Operator.ASSIGN; - break; - - case ' ': - case '\t': - // ignore whitespaces - break; - default: - sb.append(c); - } - } else { - switch (c) { - case '\\': - // escape char, take the next char as is - stateOnEscape = state; - state = ParseState.ESCAPE; - break; - - case '\r': - if ((ndx < len) && (in.charAt(ndx) == '\n')) { - ndx++; - } - case '\n': - if (state == ParseState.ESCAPE_NEWLINE) { - sb.append(escapeNewLineValue); - if (!ignorePrefixWhitespacesOnNewLine) { - state = ParseState.VALUE; - } - } else { - add(currentSection, key, sb, true, operator); - sb.setLength(0); - key = null; - operator = Operator.ASSIGN; - - // end of value, continue to text - state = ParseState.TEXT; - } - break; - - case ' ': - case '\t': - if (state == ParseState.ESCAPE_NEWLINE) { - break; - } - default: - sb.append(c); - state = ParseState.VALUE; - - if (multilineValues) { - if (sb.length() == 3) { - - // check for ''' beginning - if (sb.toString().equals("'''")) { - sb.setLength(0); - int endIndex = in.indexOf("'''", ndx); - if (endIndex == -1) { - endIndex = in.length(); - } - sb.append(in, ndx, endIndex); - - // append - add(currentSection, key, sb, false, operator); - sb.setLength(0); - key = null; - operator = Operator.ASSIGN; - - // end of value, continue to text - state = ParseState.TEXT; - ndx = endIndex + 3; - } - } - } - } - } - } - - if (key != null) { - add(currentSection, key, sb, true, operator); - } - } - - // ---------------------------------------------------------------- add - - /** - * Adds accumulated value to key and current section. - */ - protected void add( - final String section, final String key, - final StringBuilder value, final boolean trim, final Operator operator) { - - // ignore lines without : or = - if (key == null) { - return; - } - String fullKey = key; - - if (section != null) { - if (fullKey.length() != 0) { - fullKey = section + '.' + fullKey; - } else { - fullKey = section; - } - } - String v = value.toString(); - - if (trim) { - if (valueTrimLeft && valueTrimRight) { - v = v.trim(); - } else if (valueTrimLeft) { - v = StringUtil.trimLeft(v); - } else { - v = StringUtil.trimRight(v); - } - } - - if (v.length() == 0 && skipEmptyProps) { - return; - } - - extractProfilesAndAdd(fullKey, v, operator); - } - - /** - * Extracts profiles from the key name and adds key-value to them. - */ - protected void extractProfilesAndAdd(final String key, final String value, final Operator operator) { - String fullKey = key; - int ndx = fullKey.indexOf(PROFILE_LEFT); - if (ndx == -1) { - justAdd(fullKey, value, null, operator); - return; - } - - // extract profiles - final ArrayList keyProfiles = new ArrayList<>(); - - while (true) { - ndx = fullKey.indexOf(PROFILE_LEFT); - if (ndx == -1) { - break; - } - - final int len = fullKey.length(); - - int ndx2 = fullKey.indexOf(PROFILE_RIGHT, ndx + 1); - if (ndx2 == -1) { - ndx2 = len; - } - - // remember profile - final String profile = fullKey.substring(ndx + 1, ndx2); - keyProfiles.add(profile); - - // extract profile from key - ndx2++; - final String right = (ndx2 == len) ? StringPool.EMPTY : fullKey.substring(ndx2); - fullKey = fullKey.substring(0, ndx) + right; - } - - if (fullKey.startsWith(StringPool.DOT)) { - // check for special case when only profile is defined in section - fullKey = fullKey.substring(1); - } - - // add value to extracted profiles - justAdd(fullKey, value, keyProfiles, operator); - } - - /** - * Core key-value addition. - */ - protected void justAdd(final String key, final String value, final ArrayList keyProfiles, final Operator operator) { - if (operator == Operator.COPY) { - final HashMap target = new HashMap<>(); - - String[] profiles = null; - if (keyProfiles != null) { - profiles = keyProfiles.toArray(new String[0]); - } - - final String[] sources = StringUtil.splitc(value, ','); - for (String source : sources) { - source = source.trim(); - - // try to extract profile for parsing - - String[] lookupProfiles = profiles; - String lookupProfilesString = null; - - final int leftIndex = source.indexOf('<'); - if (leftIndex != -1) { - final int rightIndex = source.indexOf('>'); - - lookupProfilesString = source.substring(leftIndex + 1, rightIndex); - source = source.substring(0, leftIndex).concat(source.substring(rightIndex + 1)); - - lookupProfiles = StringUtil.splitc(lookupProfilesString, ','); - - StringUtil.trimAll(lookupProfiles); - } - - final String[] wildcards = new String[] {source + ".*"}; - - propsData.extract(target, lookupProfiles, wildcards, null); - - for (final Map.Entry entry : target.entrySet()) { - final String entryKey = entry.getKey(); - final String suffix = entryKey.substring(source.length()); - - final String newKey = key + suffix; - - String newValue = "${" + entryKey; - if (lookupProfilesString != null) { - newValue += "<" + lookupProfilesString + ">"; - } - newValue += "}"; - - if (profiles == null) { - propsData.putBaseProperty(newKey, newValue, false); - } else { - for (final String p : profiles) { - propsData.putProfileProperty(newKey, newValue, p, false); - } - } - } - } - return; - } - - final boolean append = operator == Operator.QUICK_APPEND; - if (keyProfiles == null) { - propsData.putBaseProperty(key, value, append); - return; - } - for (final String p : keyProfiles) { - propsData.putProfileProperty(key, value, p, append); - } - - } - -} diff --git a/jodd-props/src/main/java/jodd/props/package-info.java b/jodd-props/src/main/java/jodd/props/package-info.java deleted file mode 100644 index 0eb879737..000000000 --- a/jodd-props/src/main/java/jodd/props/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -/** - * Super properties. - */ -package jodd.props; \ No newline at end of file diff --git a/jodd-props/src/main/resources/META-INF/LICENSE b/jodd-props/src/main/resources/META-INF/LICENSE deleted file mode 100644 index a040a3b95..000000000 --- a/jodd-props/src/main/resources/META-INF/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2003-present, Jodd Team (https://jodd.org) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/jodd-props/src/test/java/jodd/props/BasePropsTest.java b/jodd-props/src/test/java/jodd/props/BasePropsTest.java deleted file mode 100644 index a44ece890..000000000 --- a/jodd-props/src/test/java/jodd/props/BasePropsTest.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.io.FastCharArrayWriter; -import jodd.io.IOUtil; -import jodd.util.ResourcesUtil; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; - -abstract class BasePropsTest { - - protected InputStream readDataToInputstream(final String fileName) throws IOException { - String dataFolder = this.getClass().getPackage().getName() + ".data."; - dataFolder = dataFolder.replace('.', '/'); - return ResourcesUtil.getResourceAsStream(dataFolder + fileName); - } - - protected File readDataToFile(final String fileName) throws URISyntaxException { - String dataFolder = this.getClass().getPackage().getName() + ".data."; - dataFolder = dataFolder.replace('.', '/'); - - final URL url = ResourcesUtil.getResourceUrl("/" + dataFolder + fileName); - return new File(url.toURI()); - } - - protected String readDataFile(final String fileName) throws IOException { - String dataFolder = this.getClass().getPackage().getName() + ".data."; - dataFolder = dataFolder.replace('.', '/'); - - final InputStream is = ResourcesUtil.getResourceAsStream(dataFolder + fileName); - final Writer out = new FastCharArrayWriter(); - String encoding = "UTF-8"; - if (fileName.endsWith(".properties")) { - encoding = "ISO-8859-1"; - } - IOUtil.copy(is, out, Charset.forName(encoding)); - IOUtil.close(is); - return out.toString(); - } - - protected Props loadProps(final Props p, final String fileName) throws IOException { - String dataFolder = this.getClass().getPackage().getName() + ".data."; - dataFolder = dataFolder.replace('.', '/'); - - final InputStream is = ResourcesUtil.getResourceAsStream(dataFolder + fileName); - String encoding = "UTF-8"; - if (fileName.endsWith(".properties")) { - encoding = "ISO-8859-1"; - } - p.load(is, encoding); - return p; - } - - protected Props loadProps(final String fileName) throws IOException { - final Props p = new Props(); - return loadProps(p, fileName); - } -} diff --git a/jodd-props/src/test/java/jodd/props/PropertiesToPropsTestHelper.java b/jodd-props/src/test/java/jodd/props/PropertiesToPropsTestHelper.java deleted file mode 100644 index fb47919f9..000000000 --- a/jodd-props/src/test/java/jodd/props/PropertiesToPropsTestHelper.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -public class PropertiesToPropsTestHelper { - - private PropertiesToPropsTestHelper() { - } - - public static String safelyWritePropertiesToProps(final Properties baseProperties) throws IOException { - final StringWriter writer = new StringWriter(); - safelyWritePropertiesToProps(writer, baseProperties); - return writer.toString(); - } - - public static void safelyWritePropertiesToProps(final Writer writer, final Properties baseProperties) - throws IOException { - try { - PropsConverter.convert(writer, baseProperties); - } finally { - try { - writer.close(); - } catch (IOException e) { - // close quietly - } - } - } - - public static String safelyWritePropertiesToProps(final Properties baseProperties, - final Map profiles) throws IOException { - final StringWriter writer = new StringWriter(); - safelyWritePropertiesToProps(writer, baseProperties, profiles); - return writer.toString(); - } - - public static void safelyWritePropertiesToProps(final Writer writer, final Properties baseProperties, - final Map profiles) throws IOException { - try { - PropsConverter.convert(writer, baseProperties, profiles); - } finally { - try { - writer.close(); - } catch (IOException e) { - // close quietly - } - } - } - - // todo: implement equals and hashCode in Jodd Props, PropsData and PropsEntry then use assertEqualProps - public static void assertEqualProps(final String actual, final String expectedResourceFileName) { - final Props actualProps = new Props(); - actualProps.load(actual); - - final Props expectedProps = new Props(); - try { - expectedProps.load(getResourceFile(expectedResourceFileName)); - } catch (IOException | URISyntaxException e) { - fail(e.getMessage()); - throw new IllegalStateException(e); - } - assertEquals(expectedProps, actualProps); - } - - public static void assertEqualsToPropsFile(final String actual, final String resourceNameWithExpectedFileContent) - throws URISyntaxException { - assertEqualsToPropsFile(actual, getResourceFile(resourceNameWithExpectedFileContent)); - } - - public static File getResourceFile(final String name) throws URISyntaxException { - final URL resourceFile = PropertiesToPropsTestHelper.class.getResource(name); - return new File(resourceFile.toURI()); - } - - public static void assertEqualsToPropsFile(final String actual, final File expectedFile) { - final FileReader reader = getFileReader(expectedFile); - final BufferedReader bufferedReader = new BufferedReader(reader); - final String expected; - try { - expected = readContent(bufferedReader); - } catch (IOException e) { - fail(e.getMessage()); - throw new IllegalStateException(e); - } - - String actualUnixStyle = actual.replace("\r\n", "\n"); - assertEquals(expected, actualUnixStyle); - } - - private static String readContent(final BufferedReader reader) throws IOException { - String content = ""; - String line; - while ((line = reader.readLine()) != null) { - content += line; - // Mimic Props writer functionality - content += '\n'; - } - return content; - } - - private static FileReader getFileReader(final File file) { - final FileReader reader; - try { - reader = new FileReader(file); - } catch (FileNotFoundException e) { - fail(e.getMessage()); - throw new IllegalStateException(e); - } - return reader; - } -} diff --git a/jodd-props/src/test/java/jodd/props/Props141Test.java b/jodd-props/src/test/java/jodd/props/Props141Test.java deleted file mode 100644 index 6927f5db6..000000000 --- a/jodd-props/src/test/java/jodd/props/Props141Test.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.util.StringPool; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class Props141Test extends BasePropsTest { - - @Test - void test141Simple() throws IOException { - Props props = new Props(); - String data = readDataFile("i141.props"); - props.load(data); - - assertEquals("value1", props.getValue("key1")); - - assertNull(props.getValue(".key1", "ONE")); - assertEquals("value1#ONE", props.getValue("key1", "ONE")); - assertEquals("value1", props.getValue("key1", "qwe", null)); - } - - @Test - void test141Complex() throws IOException { - Props props = new Props(); - String data = readDataFile("i141-2.props"); - props.load(data); - - // Without profile, and using ERROR profile - assertEquals("NOT AN ERROR 1", props.getValue("code", StringPool.EMPTY)); - assertEquals("NOT AN ERROR 2", props.getValue("label", StringPool.EMPTY)); - assertEquals("NOT AN ERROR 3", props.getValue("details", StringPool.EMPTY)); - - assertEquals("#UNDEFINED", props.getValue("code", "ERROR")); - assertEquals("UNDEFINED LABEL", props.getValue("label", "ERROR")); - assertEquals("UNDEFINED DETAILS", props.getValue("details", "ERROR")); - - // Using the ERROR.ONE inner profile - assertEquals("#ONE", props.getValue("code", "ERROR.ONE")); - assertEquals("THIS IS ERROR #ONE", props.getValue("label", "ERROR.ONE")); - assertEquals("UNDEFINED DETAILS", props.getValue("details", "ERROR.ONE")); - - // Now, using ERROR.TWO inner profile, which uses another syntax: - assertEquals("#TWO", props.getValue("code", "ERROR.TWO")); - assertEquals("THIS IS ERROR #TWO", props.getValue("label", "ERROR.TWO")); - assertEquals("UNDEFINED DETAILS", props.getValue("details", "ERROR.TWO")); - - // trying to use a third inner profile, not defined in the properties - assertEquals("#UNDEFINED", props.getValue("code", "ERROR.THREE")); - assertEquals("UNDEFINED LABEL", props.getValue("label", "ERROR.THREE")); - assertEquals("UNDEFINED DETAILS", props.getValue("details", "ERROR.THREE")); - } -} diff --git a/jodd-props/src/test/java/jodd/props/Props146Test.java b/jodd-props/src/test/java/jodd/props/Props146Test.java deleted file mode 100644 index e0c0d48af..000000000 --- a/jodd-props/src/test/java/jodd/props/Props146Test.java +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class Props146Test { - - @Test - void testIssue146ActiveProfile() { - String data = - "root=/app\n" + - "root=/foo\n" + - "root=/bar\n" + - "data.path=${root}/data"; - - Props props = new Props(); - - props.load(data); - - assertEquals("/app", props.getValue("root")); - assertEquals("/app/data", props.getValue("data.path")); - - // set active profile, now we expect - props.setActiveProfiles("foo"); - - assertEquals("/foo", props.getValue("root")); - assertEquals("/foo/data", props.getValue("data.path")); - - // different active profile - props.setActiveProfiles("bar"); - - assertEquals("/bar", props.getValue("root")); - assertEquals("/bar/data", props.getValue("data.path")); - } - - @Test - void testIssue146DeclaredProfile() { - String data = - "root=/app\n" + - "root=/foo\n" + - "data.path=${root}/data\n" + - "data.path=${root}/data" - ; - - Props props = new Props(); - props.load(data); - - assertEquals("/app", props.getValue("root")); - assertEquals("/app/data", props.getValue("data.path")); - - props.setActiveProfiles("foo"); - - assertEquals("/foo", props.getValue("root")); - assertEquals("/foo/data", props.getValue("data.path")); - } - - @Test - void testIssue146Directly() { - String data = - "root=/app\n" + - "root=/foo\n" + - "data.path=${root}/data"; - - Props props = new Props(); - props.load(data); - - assertEquals("/app", props.getValue("root")); - assertEquals("/app/data", props.getValue("data.path")); - - assertEquals("/foo", props.getValue("root", "foo")); - assertEquals("/foo/data", props.getValue("data.path", "foo")); - } - - @Test - void testAddonFor146() { - String data = - "key1=DEFAULT\n" + - "key1=FOO\n" + - "\n" + - "key2=${key1}\n" + - "\n" + - "key3=${key1}\n" + - "\n" + - "key4=${key1}\n" + - "key4=${key1}BAR\n" + - "\n" + - "[group1]\n" + - "key=DEFAULT\n" + - "key=FOO\n" + - "[group2]\n" + - "<= group1"; - - Props props = new Props(); - props.load(data); - - assertEquals("FOO", props.getValue("key1", "foo")); - assertEquals("DEFAULT", props.getValue("key1")); - - assertEquals("FOO", props.getValue("key3")); - assertEquals("FOO", props.getValue("key3", "foo")); - assertEquals("FOO", props.getValue("key3", "foo", "bar")); - - assertEquals("FOO", props.getValue("key4", "foo")); - assertEquals("FOOBAR", props.getValue("key4", "bar")); - assertEquals("DEFAULT", props.getValue("key4")); - - assertEquals("FOO", props.getValue("group2.key")); // == ${group1.key} - - } - -} diff --git a/jodd-props/src/test/java/jodd/props/Props610Test.java b/jodd-props/src/test/java/jodd/props/Props610Test.java deleted file mode 100644 index 06e3e04f0..000000000 --- a/jodd-props/src/test/java/jodd/props/Props610Test.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class Props610Test { - - @Test - void testMapKey_withEscape() { - final Props props = new Props(); - props.load("map\\[k1]=v1"); - - assertEquals("map[k1]", props.entries().iterator().next().getKey()); - } - - @Test - void testMapKey_withoutEscape() { - final Props props = new Props(); - props.load("map[k1]=v1"); - - assertEquals("map[k1]", props.entries().iterator().next().getKey()); - } - - @Test - void testMapKey_withoutSpaces() { - final Props props = new Props(); - props.load(" [k1]=v1"); - - assertEquals("k1", props.entries().iterator().next().getKey()); - } - -} diff --git a/jodd-props/src/test/java/jodd/props/PropsBeanTest.java b/jodd-props/src/test/java/jodd/props/PropsBeanTest.java deleted file mode 100644 index 74224268f..000000000 --- a/jodd-props/src/test/java/jodd/props/PropsBeanTest.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.bean.BeanCopy; -import jodd.bean.BeanUtil; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class PropsBeanTest { - - public static class HttpConfig { - public int port; - public String address; - public int pool; - } - - @Test - void testInnerMapToBean() { - final String data = "http.port=10101\n" + - "http.address=localhost\n" + - "http.pool=30\n" + - "foo=bar"; - - Props props = new Props(); - props.load(data); - - final Map innerMap = props.innerMap("http"); - assertEquals(3, innerMap.size()); - - final HttpConfig httpConfig = new HttpConfig(); - - BeanCopy.from(innerMap).to(httpConfig).copy(); - - assertEquals(10101, httpConfig.port); - assertEquals(30, httpConfig.pool); - assertEquals("localhost", httpConfig.address); - - // back - - props = new Props(); - props.addInnerMap("http", innerMap); - - assertEquals("10101", props.getValue("http.port")); - assertEquals("30", props.getValue("http.pool")); - assertEquals("localhost", props.getValue("http.address")); - } - - @Test - void testToBean() { - final String data = "port=10101\n" + - "address=localhost\n" + - "pool=30\n" + - "foo=bar"; - - final Props props = new Props(); - props.load(data); - - final HttpConfig httpConfig = new HttpConfig(); - - props.entries().forEach(pe -> BeanUtil.silent.setProperty(httpConfig, pe.getKey(), pe.getValue())); - - assertEquals(10101, httpConfig.port); - assertEquals(30, httpConfig.pool); - assertEquals("localhost", httpConfig.address); - } -} diff --git a/jodd-props/src/test/java/jodd/props/PropsLoaderTest.java b/jodd-props/src/test/java/jodd/props/PropsLoaderTest.java deleted file mode 100644 index 53f88eaa9..000000000 --- a/jodd-props/src/test/java/jodd/props/PropsLoaderTest.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import jodd.test.DisabledOnJava; -import org.junit.jupiter.api.Test; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.net.URISyntaxException; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Properties; - -import static jodd.props.PropertiesToPropsTestHelper.assertEqualsToPropsFile; -import static jodd.props.PropertiesToPropsTestHelper.safelyWritePropertiesToProps; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -class PropsLoaderTest { - - private static final String PROPSUTIL_CONVERT_PATH = "propsutil/convert"; - - @Test - void testCanUseBufferedWriterToWriteBasePropertiesToProps() throws IOException, URISyntaxException { - final Properties properties = new Properties(); - properties.setProperty("myOneProperty", "and it's value"); - - final StringWriter stringWriter = new StringWriter(); - final BufferedWriter writer = new BufferedWriter(stringWriter); - safelyWritePropertiesToProps(writer, properties); - - final String expectedResourceFileName = getResourcePath("/singleProperty.props"); - final String actual = stringWriter.toString(); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteBasePropertiesToProps() throws IOException, URISyntaxException { - final Properties properties = new Properties(); - properties.setProperty("myOneProperty", "and it's value"); - - final String actual = safelyWritePropertiesToProps(properties); - final String expectedResourceFileName = getResourcePath("singleProperty.props"); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteBaseWithProfilePropertiesToProps() throws IOException, URISyntaxException { - final Properties baseProperties = new Properties(); - baseProperties.setProperty("myOneProperty", "and it's value"); - - final Properties productionProperties = new Properties(); - productionProperties.setProperty("myOneProperty", "I've got production written all over me"); - - final Map profiles = new LinkedHashMap() { - { - put("production", productionProperties); - } - }; - final String actual = safelyWritePropertiesToProps(baseProperties, profiles); - final String expectedResourceFileName = getResourcePath("oneProfile.props"); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteBaseWithTwoProfilePropertiesToProps() throws IOException, URISyntaxException { - final Properties baseProperties = new Properties(); - baseProperties.setProperty("myOneProperty", "and it's value"); - - final Properties productionProperties = new Properties(); - productionProperties.setProperty("myOneProperty", "I've got production written all over me"); - - final Properties testProperties = new Properties(); - testProperties.setProperty("myOneProperty", "TEST TEST TEST!!"); - - final Map profiles = new LinkedHashMap() { - { - put("production", productionProperties); - put("test", testProperties); - } - }; - final String actual = safelyWritePropertiesToProps(baseProperties, profiles); - final String expectedResourceFileName = getResourcePath("twoProfiles.props"); - -// assertEqualProps(actual, expectedResourceFileName); - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteMoreProfileThanBasePropertiesToProps() throws IOException, URISyntaxException { - final Properties baseProperties = new Properties(); - baseProperties.setProperty("myOneProperty", "and it's value"); - - final Properties productionProperties = new Properties(); - productionProperties.setProperty("mySecondProperty", "I've got production written all over me"); - - final Properties testProperties = new Properties(); - testProperties.setProperty("mySecondProperty", "TEST TEST TEST!!"); - - final Map profiles = new LinkedHashMap() { - { - put("production", productionProperties); - put("test", testProperties); - } - }; - final String actual = safelyWritePropertiesToProps(baseProperties, profiles); - final String expectedResourceFileName = getResourcePath("moreProfilePropertiesThanBase.props"); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteMultilineValuesToProps() throws IOException, URISyntaxException { - final Properties baseProperties = new Properties(); - baseProperties.setProperty("myOneProperty", "long value\\\nin two lines"); - - final String actual = safelyWritePropertiesToProps(baseProperties); - final String expectedResourceFileName = getResourcePath("multilineValue.props"); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - void testCanWriteUtf8ValuesToProps() throws IOException, URISyntaxException { - final Properties baseProperties = new Properties(); - baseProperties.setProperty("myOneProperty", "some utf8 \\u0161\\u0111\\u017e\\u010d\\u0107"); - - final String actual = safelyWritePropertiesToProps(baseProperties); - final String expectedResourceFileName = getResourcePath("utf8Value.props"); - - assertEqualsToPropsFile(actual, expectedResourceFileName); - } - - @Test - @DisabledOnJava(value = 9, description = "Classpath loading only works with MR-JAR jars as they don't work in exploded mode.") - void testCreateFromClasspath_WithExistingFileThroughPattern() { - final Props actual = Props.create().loadFromClasspath("*jodd/props/data/test.properties"); - - // asserts - assertNotNull(actual); - assertEquals(3, actual.countTotalProperties()); - assertEquals("value", actual.getValue("one")); - assertEquals("long valuein two lines", actual.getValue("two")); - assertEquals("some utf8 šđžčć", actual.getValue("three")); - - } - - @Test - void testCreateFromClasspath_WithNotExistingFileThroughPattern() { - - final Props actual = Props.create().loadFromClasspath("*jodd/props/data/test_properties"); - - // asserts - assertNotNull(actual); - assertEquals(0, actual.countTotalProperties()); - assertNull(actual.getValue("one")); - } - - - private String getResourcePath(final String name) { - return PROPSUTIL_CONVERT_PATH + "/" + name; - } - -} diff --git a/jodd-props/src/test/java/jodd/props/PropsTest.java b/jodd-props/src/test/java/jodd/props/PropsTest.java deleted file mode 100644 index b59f39aa6..000000000 --- a/jodd-props/src/test/java/jodd/props/PropsTest.java +++ /dev/null @@ -1,1126 +0,0 @@ -// Copyright (c) 2003-present, Jodd Team (http://jodd.org) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -package jodd.props; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class PropsTest extends BasePropsTest { - - @Test - void testBasic() throws IOException { - Props p = new Props(); - p.load(readDataFile("test.props")); - - assertEquals(17, p.countTotalProperties()); - - assertEquals("Snow White and the Seven Dwarfs", p.getValue("story")); - assertEquals("Walt Disney's New characters in his first full-length production!", p.getValue("Tagline")); - assertEquals("C:\\local\\snowwhite.mpg", p.getValue("file")); - assertEquals("Snow White, pursued by a jealous queen, hides with the Dwarfs; the queen feeds her a poison apple, but Prince Charming awakens her with a kiss.", p.getValue("plot")); - - assertEquals("45.7", p.getValue("bashful.weight")); - assertEquals("49.5", p.getValue("doc.weight")); - - assertEquals("Čađavi Žar utf8", p.getValue("comment")); - - assertEquals("foo\tboo\rzoo\nxxx\ftoo", p.getValue("special-chars")); - assertEquals("\\\\a", p.getValue("special2")); - assertEquals(3, p.getValue("special2").length()); - - assertNull(p.getValue("non existing")); - - Properties prop = new Properties(); - p.extractProps(prop, null); - assertEquals("1937{c}", prop.getProperty("year")); - assertEquals("49.5", prop.getProperty("doc.weight")); - assertEquals("Čađavi Žar utf8", prop.getProperty("comment")); - } - - @Test - void testEscapeNewValue() throws IOException { - Props p = new Props(); - p.setEscapeNewLineValue("
"); - p.load(readDataFile("test.props")); - assertEquals("Snow White, pursued by a jealous queen, hides with the Dwarfs;
the queen feeds her a poison apple, but Prince Charming
awakens her with a kiss.", p.getValue("plot")); - } - - @Test - void testIgnorePrefixWhitespace() throws IOException { - Props p = new Props(); - p.setIgnorePrefixWhitespacesOnNewLine(false); - p.load(readDataFile("test.props")); - assertEquals("Snow White, pursued by a jealous queen, hides with the Dwarfs; \t\tthe queen feeds her a poison apple, but Prince Charming \t\tawakens her with a kiss.", p.getValue("plot")); - } - - @Test - void testProfiles() throws IOException { - Props p = new Props(); - p.load(readDataFile("test-profiles.props")); - - assertEquals("one", p.getValue("foo")); - assertEquals("one", p.getValue("foo", "non_existing_profile")); - assertEquals("one", p.getValue("foo", "qwe")); - assertEquals("ten", p.getValue("bar")); - - assertEquals("12345", p.getValue("vitamine", "aaa")); - - assertEquals(8, p.countTotalProperties()); - - assertNull(p.getValue("db.url")); - assertEquals("localhost", p.getValue("db.url", "develop")); - assertEquals("localhost", p.getValue("db.url", "develop", "deploy")); - assertEquals("192.168.1.102", p.getValue("db.url", "deploy", "develop")); - assertEquals("192.168.1.102", p.getValue("db.url", "deploy")); - - Properties prop = new Properties(); - p.extractProps(prop, null); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.extractProps(prop, "non_existing"); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.extractProps(prop, "aaa"); - assertEquals("12345", prop.getProperty("vitamine")); - - prop.clear(); - p.extractProps(prop, "develop"); - assertEquals("localhost", prop.getProperty("db.url")); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.extractProps(prop, "develop", "deploy"); - assertEquals("localhost", prop.getProperty("db.url")); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.extractProps(prop, "deploy", "develop"); - assertEquals("192.168.1.102", prop.getProperty("db.url")); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.extractProps(prop, "deploy"); - assertEquals("192.168.1.102", prop.getProperty("db.url")); - assertEquals("one", prop.getProperty("foo")); - - prop.clear(); - p.setActiveProfiles("deploy"); - p.extractSubProps(prop, "db.*"); - assertEquals(2, prop.size()); - } - - @Test - void testDefaultProfile() { - Props p = new Props(); - p.load( - "key1=hello\n" + - "key1=Hi!\n" + - " \n" + - "@profiles=one"); - - assertEquals("Hi!", p.getValue("key1")); - assertEquals("Hi!", p.getValue("key1", "one")); - } - - @Test - void testNestedProfiles() throws IOException { - Props p = new Props(); - p.load(readDataFile("test-profiles.props")); - - assertEquals("hello", p.getBaseValue("key1")); - assertEquals("hello", p.getValue("key1")); - assertEquals("Hi!", p.getValue("key1", "one")); - assertEquals("Hola!", p.getValue("key1", "one.two")); - assertEquals("world", p.getValue("key2", "one.two")); - assertNull(p.getValue("key2", "one")); - assertEquals("Grazias", p.getValue("key3", "one.two")); - assertEquals("Grazias", p.getValue("key3", "one")); - - Properties prop = new Properties(); - p.extractProps(prop); - assertEquals(3, prop.size()); - assertEquals("hello", prop.getProperty("key1")); - - prop.clear(); - p.extractProps(prop, "one"); - assertEquals(3 + 1, prop.size()); - assertEquals("Hi!", prop.getProperty("key1")); - assertEquals("Grazias", prop.getProperty("key3")); - - prop.clear(); - p.extractProps(prop, "one.two"); - assertEquals(3 + 2, prop.size()); - assertEquals("Hola!", prop.getProperty("key1")); - assertEquals("world", prop.getProperty("key2")); - assertEquals("Grazias", prop.getProperty("key3")); - } - - @Test - void testMacros() throws IOException { - Props p = new Props(); - p.load(readDataFile("test2.props")); - - assertEquals("/roo/mypath", p.getValue("data.mypath")); - - assertEquals("/app/data", p.getValue("data.path")); - assertEquals("/app/data2", p.getValue("data.path", "@prof1")); - assertEquals("/foo/data3", p.getValue("data.path", "@prof2")); - - assertEquals("/roo/re", p.getValue("data.path", "@p1")); - assertEquals("/app/re", p.getValue("data.path", "@p2")); - - Properties prop = new Properties(); - p.extractProps(prop, "@prof2"); - assertEquals("/foo/data3", prop.getProperty("data.path")); - } - - @Test - void testMacrosNew() throws IOException { - Props p = new Props(); - p.load(readDataFile("test2.props")); - - assertEquals("/app/data", p.getValue("data.path")); - assertEquals("/app/data2", p.getValue("data.path", "@prof1")); - assertEquals("/foo/data3", p.getValue("data.path", "@prof2")); - - assertEquals("/roo/re", p.getValue("data.path", "@p1")); - assertEquals("/app/re", p.getValue("data.path", "@p2")); - - Properties prop = new Properties(); - p.extractProps(prop, "@prof2"); - assertEquals("/foo/data3", prop.getProperty("data.path")); - - // activate profiles - - p.setActiveProfiles("@prof2"); - assertEquals("/foo/data3", p.getValue("data.path", "@prof2")); - - p.setActiveProfiles("@p1", "@p2"); - assertEquals("/app/re", p.getValue("data.path", "@p2")); - } - - - @Test - void testMacros2() throws IOException { - Props p = new Props(); - p.setValue("key1", "**${key${key3}}**"); - p.setValue("key3", "2"); - p.setValue("key2", "++++"); - - assertEquals("**++++**", p.getValue("key1")); - } - - @Test - void testMacroNotExist() { - Props p = new Props(); - p.setValue("mac1", "value1"); - p.setValue("key1", "${mac1}"); - p.setValue("key2", "${mac2}"); - - assertEquals("value1", p.getValue("mac1")); - assertEquals("value1", p.getValue("key1")); - assertEquals("${mac2}", p.getValue("key2")); - } - - @Test - void testMacroNotExistIgnoreMissing() { - Props p = new Props(); - p.setIgnoreMissingMacros(true); - p.setValue("mac1", "value1"); - p.setValue("key1", "${mac1}"); - p.setValue("key2", "${mac2}"); - - assertEquals("value1", p.getValue("mac1")); - assertEquals("value1", p.getValue("key1")); - assertNull(p.getValue("key2")); - } - - @Test - void testMacroNotExistSkipEmpty() { - Props p = new Props(); - p.setIgnoreMissingMacros(true); - p.setSkipEmptyProps(false); - p.setValue("mac1", "value1"); - p.setValue("key1", "${mac1}"); - p.setValue("key2", "${mac2}"); - - assertEquals("value1", p.getValue("mac1")); - assertEquals("value1", p.getValue("key1")); - assertEquals("", p.getValue("key2")); - } - - @Test - void testClone() throws IOException { - Props p = new Props(); - p.load(readDataFile("test2.props")); - - Props p2 = p.clone(); - p2.load(readDataFile("test.props")); - - assertEquals(3, p.countTotalProperties()); - assertEquals(20, p2.countTotalProperties()); - - assertEquals("/app/data", p.getValue("data.path")); - assertEquals("/app/data2", p.getValue("data.path", "@prof1")); - assertEquals("/foo/data3", p.getValue("data.path", "@prof2")); - } - - @Test - void testEmpty() throws IOException { - Props p = new Props(); - p.setSkipEmptyProps(false); - p.load(readDataFile("test-e.props")); - - assertEquals(2, p.countTotalProperties()); - assertEquals("good", p.getValue("ok")); - assertEquals("", p.getValue("empty")); - } - - @Test - void testActiveProfiles() throws IOException { - Props p = loadProps("test-actp.props"); - - assertEquals("hello", p.getBaseValue("key1")); - assertEquals("Hola!", p.getValue("key1")); - assertEquals("world", p.getValue("key2")); - - assertEquals(1, p.getActiveProfiles().length); - assertEquals("one.two", p.getActiveProfiles()[0]); - } - - @Test - void testProperties() throws IOException { - Props p = loadProps("test.properties"); - - assertEquals("value", p.getValue("one")); - assertEquals("long valuein two lines", p.getValue("two")); - assertEquals("some utf8 šđžčć", p.getValue("three")); - } - - @Test - void testAdd() { - Props p = new Props(); - p.setValue("key1", "val${key2}"); - - assertEquals("val${key2}", p.getValue("key1")); - assertNull(p.getValue("key1${key2}")); - - p.setValue("key2", "hurrey\tme!"); - - assertEquals("valhurrey\tme!", p.getValue("key1")); - } - - @Test - void testDuplicate() throws IOException { - Props p = new Props(); - loadProps(p, "test-dupl.props"); - - assertEquals("three", p.getValue("foo")); - assertEquals("everywhere", p.getValue("bar", "prof")); - - p = new Props(); - p.setAppendDuplicateProps(true); - loadProps(p, "test-dupl.props"); - - assertEquals("one,two,three", p.getValue("foo")); - assertEquals("here,there,everywhere", p.getValue("bar", "prof")); - } - - @Test - void testDoubleLoadsAndResolves() { - Props props = new Props(); - props.load("pojoBean2.val2=123"); - props.load("pojoBean2.val1=\\\\${pojo}"); - - assertEquals("123", props.getValue("pojoBean2.val2")); - // BeanTemplate resolves \${foo} to ${foo} - // we must be sure that escaped value is not resolved. - assertEquals("\\${pojo}", props.getValue("pojoBean2.val1")); - - props.load("pojoBean2.val1=\\\\${pojo} ${pojo}"); - assertEquals(2, props.countTotalProperties()); - assertEquals("\\${pojo} ${pojo}", props.getValue("pojoBean2.val1")); - } - - @Test - void testSystemProperties() { - Props props = new Props(); - assertEquals(0, props.countTotalProperties()); - assertNull(props.getValue("user.dir")); - - props.loadSystemProperties("sys"); - assertTrue(props.countTotalProperties() > 0); - assertNotNull(props.getValue("sys.user.dir")); - } - - @Test - void testEnvironment() { - Props props = new Props(); - assertEquals(0, props.countTotalProperties()); - - props.loadEnvironment("env"); - assertTrue(props.countTotalProperties() > 0); - } - - @Test - void testValueWithBracket() throws IOException { - Props p = new Props(); - p.load(readDataFile("test3.props")); - - assertEquals("info@jodd.org;patrick@jodd.org", p.getValue("email.from")); - assertEquals("[ERROR] Got %s exceptions", p.getValue("email.subject")); - assertEquals("line1line2line3", p.getValue("email.text")); - - p = new Props(); - p.setIgnorePrefixWhitespacesOnNewLine(false); - p.load(readDataFile("test3.props")); - - assertEquals("info@jodd.org;patrick@jodd.org", p.getValue("email.from")); - assertEquals("[ERROR] Got %s exceptions", p.getValue("email.subject")); - assertEquals("line1\tline2line3", p.getValue("email.text")); - - p = new Props(); - p.setIgnorePrefixWhitespacesOnNewLine(false); - p.setEscapeNewLineValue("\n"); - p.load(readDataFile("test3.props")); - - assertEquals("info@jodd.org;patrick@jodd.org", p.getValue("email.from")); - assertEquals("[ERROR] Got %s exceptions", p.getValue("email.subject")); - assertEquals("line1\n\tline2\nline3", p.getValue("email.text")); - } - - @Test - void testMultilineValue() throws IOException { - Props p = new Props(); - p.setValueTrimLeft(true); - p.load(readDataFile("test3.props")); - - assertEquals(System.lineSeparator() + "\tHello from" + System.lineSeparator() + "\tthe multiline" + System.lineSeparator() + "\tvalue" + System.lineSeparator() , p.getValue("email.footer")); - assertEquals("aaa", p.getValue("email.header")); - } - - @Test - void testAppend() { - Props p = new Props(); - p.setAppendDuplicateProps(true); - p.load("foo=123\nfoo=456"); - assertEquals("123,456", p.getValue("foo")); - - p = new Props(); - p.load("foo=123\nfoo+=456"); - assertEquals("123,456", p.getValue("foo")); - } - - @Test - void testAppend2() { - Props p = new Props(); - p.setAppendDuplicateProps(false); - p.load("foo=one\nfoo=two\nfoo+=three"); - assertEquals("two,three", p.getValue("foo")); - - p = new Props(); - p.setAppendDuplicateProps(true); - p.load("foo=one\nfoo=two\nfoo+=three"); - assertEquals("one,two,three", p.getValue("foo")); - - p = new Props(); - p.setAppendDuplicateProps(false); - p.load("foo=one\nfoo=two\nfoo+=three\nfoo=four"); - assertEquals("four", p.getValue("foo")); - } - - @Test - void testAppendEof() { - Props p = new Props(); - p.setAppendDuplicateProps(false); - p.load("foo=one\nfoo=two\nfoo+"); - assertEquals("two", p.getValue("foo")); - } - - @Test - void testActiveProfileBeforeInit() { - Props p = new Props(); - p.setActiveProfiles("xxx"); - p.load("foo=one"); - assertNotNull(p.getActiveProfiles()); - assertEquals("xxx", p.getActiveProfiles()[0]); - } - - @Test - void testDoubleInitialization() { - Props p = new Props(); - p.setValue("bar", "two.${foo}.${wer}"); - p.setValue("foo", "one"); - - assertEquals("two.one.${wer}", p.getValue("bar")); - - p.setValue("wer", "zero"); - - assertEquals("two.one.zero", p.getValue("bar")); - } - - @Test - void testCategoriesInValues() { - Props p = new Props(); - p.load( "[section]\n" + - "foo = aaa, [bbb:ccc]\n" + - "bar = teapot"); - - assertEquals("aaa, [bbb:ccc]", p.getValue("section.foo")); - assertEquals("teapot", p.getValue("section.bar")); - } - - @Test - void testDuplicatedValue() { - Props p = new Props(); - p.setValue("foo", "bar"); - p.setValue("foo", "aaa", "prof1"); - p.setValue("foo", "bbb", "prof2"); - - assertEquals("bar", p.getValue("foo")); - assertEquals("aaa", p.getValue("foo", "prof1")); - assertEquals("bbb", p.getValue("foo", "prof2")); - - assertEquals("aaa", p.getValue("foo", "prof1", "prof2")); - assertEquals("bbb", p.getValue("foo", "prof2", "prof1")); - } - - @Test - void testIteratorEmpty() { - Props p = new Props(); - - Iterator it = p.iterator(); - - assertFalse(it.hasNext()); - - try { - it.next(); - fail("error"); - } catch (Exception ignore) { - } - } - - @Test - void testIteratorSkip() { - Props p = new Props(); - - p.load("zorg=zero\n" + - "foo=one\n" + - "bar=two\n" + - "foo=zero"); - - Iterator it = p.iterator(); - - assertTrue(it.hasNext()); - - PropsEntry pe = it.next(); - assertEquals("foo", pe.getKey()); - pe = it.next(); - assertEquals("bar", pe.getKey()); - - assertFalse(it.hasNext()); - try { - it.next(); - fail("error"); - } catch (Exception ignore) { - } - - p.setActiveProfiles("prof1", "prof2"); - - it = p.iterator(); - assertEquals("zorg", it.next().getKey()); - assertEquals("foo", it.next().getKey()); - assertEquals("bar", it.next().getKey()); - assertEquals("foo", it.next().getKey()); - assertFalse(it.hasNext()); - - it = p.entries().activeProfiles().skipDuplicatesByValue().iterator(); - - assertEquals("zorg", it.next().getKey()); - assertEquals("bar", it.next().getKey()); - assertEquals("foo", it.next().getKey()); - assertFalse(it.hasNext()); - - it = p.entries().profile("prof1").skipDuplicatesByValue().iterator(); - assertEquals("bar", it.next().getKey()); - assertEquals("foo", it.next().getKey()); - assertFalse(it.hasNext()); - - - it = p.entries().activeProfiles().skipDuplicatesByPosition().iterator(); - - assertEquals("zorg", it.next().getKey()); - assertEquals("foo", it.next().getKey()); - assertEquals("bar", it.next().getKey()); - assertFalse(it.hasNext()); - - it = p.entries().profile("prof1").skipDuplicatesByPosition().iterator(); - assertEquals("foo", it.next().getKey()); - assertEquals("bar", it.next().getKey()); - assertFalse(it.hasNext()); - } - - @Test - void testIteratorSections() { - Props p = new Props(); - - p.load("aaa.zorg=zero\n" + - "bbb.foo=one\n" + - "ccc.bar=two\n" + - "bbb.foo=zero"); - - - p.setActiveProfiles("prof1", "prof2"); - - Iterator it = p.entries().section("bbb").profile("prof1", "prof2").iterator(); - assertEquals("bbb.foo", it.next().getKey()); - assertEquals("bbb.foo", it.next().getKey()); - assertFalse(it.hasNext()); - } - - @Test - void testGetAllProfiles() { - Props p = new Props(); - - p.load("zorg=zero\n" + - "foo=one\n" + - "bar=two\n" + - "foo=zero"); - - String[] profiles = p.getAllProfiles(); - Arrays.sort(profiles); - assertArrayEquals(new String[] {"prof1", "prof2"}, profiles); - } - - @Test - void testGetProfilesForKey() { - Props p = new Props(); - - p.load("zorg=zero\n" + - "foo=one\n" + - "bar=two\n" + - "[foo]\n" + - "info=zero\n" + - "info2=zero2"); - - String[] profiles = p.getProfilesFor("zorg"); - - assertEquals(1, profiles.length); - assertEquals("prof2", profiles[0]); - - profiles = p.getProfilesFor("zor*"); - - assertEquals(1, profiles.length); - assertEquals("prof2", profiles[0]); - - profiles = p.getProfilesFor("foo"); - assertEquals(0, profiles.length); - - profiles = p.getProfilesFor("foo.*"); - assertEquals(1, profiles.length); - assertEquals("prof1", profiles[0]); - - profiles = p.getProfilesFor("foo*"); - assertEquals(1, profiles.length); - assertEquals("prof1", profiles[0]); - } - - @Test - void testChangeActiveProfile() { - Props p = new Props(); - - p.load("foo=one\n" + - "bar=two\n" + - "foo=aaa\n" + - "foo=bbb\n"); - - p.setActiveProfiles("prof1"); - assertEquals("aaa", p.getValue("foo")); - - p.setActiveProfiles("prof2"); - assertEquals("bbb", p.getValue("foo")); - } - - @Test - void testWeirdKey() { - Props p = new Props(); - - p.load("org.jodd.Foo@Bar=one\n" + - "org.jodd.Foo@*Bar=two\n" + - "org.jodd.Foo@*Bar\\#me=three\n"); - - assertEquals("one", p.getValue("org.jodd.Foo@Bar")); - assertEquals("two", p.getValue("org.jodd.Foo@*Bar")); - assertEquals("three", p.getValue("org.jodd.Foo@*Bar#me")); - } - - @Test - void testMultipleProfilesAtOnce() { - Props p = new Props(); - p.load( - "foo.one=111\n" + - "foo.one=111222\n" + - "foo.one=111222333\n" - ); - - p.setActiveProfiles(null); - assertEquals("111", p.getValue("foo.one")); - - p.setActiveProfiles("pr1"); - assertEquals("111222", p.getValue("foo.one")); - - p.setActiveProfiles("pr2"); - assertEquals("111222333", p.getValue("foo.one")); - - p.setActiveProfiles("pr1", "pr2"); - assertEquals("111222", p.getValue("foo.one")); - - p.setActiveProfiles("pr2", "pr1"); - assertEquals("111222333", p.getValue("foo.one")); - } - - @Test - void testMacrosAndProfiles() { - Props p = new Props(); - p.load( - "one=111\n" + - "one=111222\n" + - "one=111222333\n" + - "wow=${one}" - ); - - p.setActiveProfiles(null); - assertEquals("111", p.getValue("wow")); - - p.setActiveProfiles("pr1"); - assertEquals("111222", p.getValue("wow")); - - p.setActiveProfiles("pr2"); - assertEquals("111222333", p.getValue("wow")); - - p.setActiveProfiles("pr1", "pr2"); - assertEquals("111222", p.getValue("wow")); - } - - @Test - void testMacrosAndProfilesAsBefore() { - Props p = new Props(); - p.load( - "one=111\n" + - "one=111222\n" + - "one=111222333\n" + - "wow=${one}" - ); - - p.setActiveProfiles(null); - assertEquals("111", p.getValue("wow")); - - p.setActiveProfiles("pr1"); - assertEquals("111222", p.getValue("wow")); - - p.setActiveProfiles("pr2"); - assertEquals("111222333", p.getValue("wow")); - - p.setActiveProfiles("pr1", "pr2"); - assertEquals("111222", p.getValue("wow")); - - // wow needs to be defined in a profile to get the profile value in macro - // NOT ANYMORE! - - p = new Props(); - p.load( - "one=111\n" + - "one=111222\n" + - "one=111222333\n" + - "wow=${one}" - ); - - p.setActiveProfiles(null); - assertEquals(null, p.getValue("wow")); - - p.setActiveProfiles("pr1"); - assertEquals("111222", p.getValue("wow")); - - p.setActiveProfiles("pr2"); - assertEquals(null, p.getValue("wow")); - - p.setActiveProfiles("pr1", "pr2"); - assertEquals("111222", p.getValue("wow")); - - - p = new Props(); - p.load( - "one=111\n" + - "one=111222\n" + - "one=111222333\n" + - "wow=${one}" - ); - - p.setActiveProfiles(null); - assertEquals(null, p.getValue("wow")); - - p.setActiveProfiles("pr1"); - assertEquals("111222", p.getValue("wow")); - - p.setActiveProfiles("pr2"); - assertEquals("111222333", p.getValue("wow")); - - p.setActiveProfiles("pr1", "pr2"); - assertEquals("111222", p.getValue("wow")); - } - - @Test - void testCopy() { - Props p = new Props(); - - p.load("foo.one=111\n" + - "fig.two=222\n" + - "bar <= foo, fig"); - - assertEquals("111", p.getValue("foo.one")); - assertEquals("222", p.getValue("fig.two")); - assertEquals("111", p.getValue("bar.one")); - assertEquals("222", p.getValue("bar.two")); - } - - @Test - void testCopyWithProfiles() { - Props p = new Props(); - p.load( - "foo.one=111\n" + - "foo.one=111111\n" + - "foo.one=111111111\n" + - "fig.two=222\n" + - "bar <= foo, fig"); - - assertEquals("111", p.getValue("foo.one")); - assertEquals(null, p.getValue("fig.two")); - assertEquals("111", p.getValue("bar.one")); - assertEquals(null, p.getValue("bar.two")); - - p = new Props(); - p.load( - "foo.one=111\n" + - "foo.one=111111\n" + - "foo.one=111111111\n" + - "fig.two=222\n" + - "bar <= foo, fig"); - - p.setActiveProfiles("pr1"); - - assertEquals("111111", p.getValue("foo.one")); - assertEquals(null, p.getValue("fig.two")); - assertEquals("111111", p.getValue("bar.one")); - assertEquals(null, p.getValue("bar.two")); - - p = new Props(); - p.load( - "foo.one=111\n" + - "foo.one=111111\n" + - "foo.one=111111111\n" + - "fig.two=222\n" + - "bar <= foo, fig\n" - ); - - p.setActiveProfiles("pr1", "pr2"); - - assertEquals("111111", p.getValue("foo.one")); - assertEquals("222", p.getValue("fig.two")); - assertEquals("111111", p.getValue("bar.one")); - assertEquals("222", p.getValue("bar.two")); - } - - @Test - void testCopyEmpty() { - Props p = new Props(); - - p.load("foo.one=111\n" + - "fig.two=222\n" + - "[bar]\n" + - "<= foo, fig"); - - assertEquals("111", p.getValue("foo.one")); - assertEquals("222", p.getValue("fig.two")); - assertEquals("111", p.getValue("bar.one")); - assertEquals("222", p.getValue("bar.two")); - } - - @Test - void testIssue78() { - String data = - "@profiles=o\n" + - "\n" + - "prefix = is Good\n" + - "prefix = is very Good\n" + - "\n" + - "[user]\n" + - "name = jodd ${prefix}"; - - Props props = new Props(); - props.load(data); - - assertEquals("jodd is Good", props.getValue("user.name")); - } - - @Test - void testAdditionalEquals() { - String data = - "account-dn = cn=accountname,ou=users,o=organization\n"; - - Props props = new Props(); - props.load(data); - - assertEquals("cn=accountname,ou=users,o=organization", props.getValue("account-dn")); - } - - - @Test - void testDifferentLineEndings() { - Props props = new Props(); - props.setIgnorePrefixWhitespacesOnNewLine(true); - props.load("text=line1\\\n line2\\\r\n line3\\\r line4"); - - assertEquals("line1line2line3line4", props.getValue("text")); - - props = new Props(); - props.setIgnorePrefixWhitespacesOnNewLine(false); - props.load("text=line1\\\n line2\\\r\n line3\\\r line4"); - - assertEquals("line1 line2 line3 line4", props.getValue("text")); - - props = new Props(); - props.setIgnorePrefixWhitespacesOnNewLine(false); - props.setEscapeNewLineValue("|"); - props.load("text=line1\\\n line2\\\r\n line3\\\r line4"); - - assertEquals("line1| line2| line3| line4", props.getValue("text")); - } - - @Test - void testLoad_with_file_props() throws IOException, URISyntaxException { - final File src = readDataToFile("test2.props"); - final Props actual = new Props().load(src); - - // asserts - assertEquals(3, actual.countTotalProperties()); - } - - @Test - void testLoad_with_file_properties() throws IOException, URISyntaxException { - final File src = readDataToFile("test.properties"); - final Props actual = new Props().load(src); - - // asserts - assertEquals(3, actual.countTotalProperties()); - } - - @Test - void testLoad_with_file_and_encoding() throws IOException, URISyntaxException { - final File src = readDataToFile("test2.props"); - final Props actual = new Props().load(src, StandardCharsets.UTF_8.name()); - - // asserts - assertEquals(3, actual.countTotalProperties()); - } - - @Test - void testLoad_with_inputstream() throws IOException { - try (final InputStream is = readDataToInputstream("test2.props")) { - final Props actual = new Props().load(is); - // asserts - assertEquals(3, actual.countTotalProperties()); - } - } - - @Nested - @DisplayName("test for Props#getXXXValue() - methods") - @TestInstance(TestInstance.Lifecycle.PER_CLASS) // needed because annotation MethodSource requires static method without that - class GetXXXValue { - - Props props; - - @BeforeEach - void beforeEach() { - props = new Props(); - Map map = new HashMap<>(); - - // test data - map.put("string_jodd", "jodd"); - map.put("boolean_true", "true"); - map.put("boolean_false", "false"); - map.put("integer_0", "0"); - map.put("integer_1234567890", "1234567890"); - map.put("integer_-45232", "-45232"); - map.put("long_0", "0"); - map.put("long_1234567890", "1234567890"); - map.put("long_-2789899", "-2789899"); - map.put("double_1234567890_12", "1234567890.12"); - map.put("double_12345678903333_34", "12345678903333.34"); - map.put("double_-43478954.44", "-43478954.44"); - - props.load(map); - } - - @ParameterizedTest (name = "{index} - Props#{1}(''{2}'') == {0}") - @MethodSource(value = "testdata") - void testGetXXXValue(final Object expected, final String methodName, final String key) throws Exception { - - Method method = props.getClass().getDeclaredMethod(methodName, String.class); - final Object actual = method.invoke(props, key); - - // asserts - assertEquals(expected, actual); - } - - @ParameterizedTest (name = "{index} - Props#{1}(''{2}'', null) == {0}") - @MethodSource(value = "testdata") - void testGetXXXValue_WithProfile(final Object expected, final String methodName, final String key) throws Exception { - - Method method = props.getClass().getDeclaredMethod(methodName, String.class, String[].class); - final Object actual = method.invoke(props, key, (String[])null); - - // asserts - assertEquals(expected, actual); - } - - @ParameterizedTest (name = "{index} - Props#{1}(''{2}'', {3}, ''{4}'') == {0}") - @MethodSource(value = "testdata_for_defaultvalues_and_profiles_test") - void testGetXXXValue_WithDefaultValueAndProfiles(final Object expected, final String methodName, final String key, final Class clazzDefaultValue, final String[] profiles) throws Exception { - Method method = props.getClass().getDeclaredMethod(methodName, String.class, expected.getClass(), String[].class); - final Object actual = method.invoke(props, key, expected, new String[] {"jodd"}); - - // asserts - assertEquals(expected, actual); - } - - @ParameterizedTest (name = "{index} - Props#{1}(''{2}'', {3}) == {0}") - @MethodSource(value = "testdata_for_defaultvalues_test") - void testGetXXXValue_WithDefaultValue(final Object expected, final String methodName, final String key, final Class clazzDefaultValue) throws Exception { - Method method = props.getClass().getDeclaredMethod(methodName, String.class, expected.getClass()); - final Object actual = method.invoke(props, key, expected); - - // asserts - assertEquals(expected, actual); - } - - private Stream testdata() { - return Stream.of( - // getValue - Arguments.of("jodd", "getValue", "string_jodd"), - Arguments.of(null, "getValue", "unknown_key"), - // getBooleanValue - Arguments.of(Boolean.TRUE, "getBooleanValue", "boolean_true"), - Arguments.of(Boolean.FALSE, "getBooleanValue", "boolean_false"), - Arguments.of(null, "getBooleanValue", "unknown_key"), - // getIntegerValue - Arguments.of(0, "getIntegerValue", "integer_0"), - Arguments.of(1234567890, "getIntegerValue", "integer_1234567890"), - Arguments.of(-45232, "getIntegerValue", "integer_-45232"), - Arguments.of(null, "getIntegerValue", "unknown_key"), - // getLongValue - Arguments.of(0L, "getLongValue", "long_0"), - Arguments.of(1234567890L, "getLongValue", "long_1234567890"), - Arguments.of(-2789899L, "getLongValue", "long_-2789899"), - Arguments.of(null, "getLongValue", "unknown_key"), - // getDoubleValue - Arguments.of(1234567890.12D, "getDoubleValue", "double_1234567890_12"), - Arguments.of(12345678903333.34D, "getDoubleValue", "double_12345678903333_34"), - Arguments.of(-43478954.44D, "getDoubleValue", "double_-43478954.44"), - Arguments.of(null, "getDoubleValue", "unknown_key") - ); - } - - private Stream testdata_for_defaultvalues_and_profiles_test() { - final String an_unknown_key = "this_is_definitely_an_unknown_key_for_test_in_props_test"; - final String[] profiles = new String[] {"jodd", "db"}; - return Stream.of( - // getBooleanValue - Arguments.of(Boolean.FALSE, "getBooleanValue", an_unknown_key, Boolean.class, profiles), - Arguments.of(Boolean.TRUE, "getBooleanValue", "boolean_true", Boolean.class, profiles), - // getIntegerValue - Arguments.of(-45232, "getIntegerValue", an_unknown_key, Integer.class, profiles), - Arguments.of(0, "getIntegerValue", "integer_0", Integer.class, profiles), - // getLongValue - Arguments.of(1234567890L, "getLongValue", an_unknown_key, Long.class, profiles), - Arguments.of(-2789899L, "getLongValue", "long_-2789899", Long.class, profiles), - // getDoubleValue - Arguments.of(-888.541D, "getDoubleValue", an_unknown_key, Double.class, profiles), - Arguments.of(1234567890.12, "getDoubleValue", "double_1234567890_12", Double.class, profiles) - ); - } - - private Stream testdata_for_defaultvalues_test() { - final String an_unknown_key = "this_is_definitely_an_unknown_key_for_test_in_props_test"; - return Stream.of( - // getBooleanValue - Arguments.of(Boolean.FALSE, "getBooleanValue", an_unknown_key, Boolean.class), - Arguments.of(Boolean.TRUE, "getBooleanValue", "boolean_true", Boolean.class), - // getIntegerValue - Arguments.of(-45232, "getIntegerValue", an_unknown_key, Integer.class), - Arguments.of(0, "getIntegerValue", "integer_0", Integer.class), - // getLongValue - Arguments.of(0L, "getLongValue", an_unknown_key, Long.class), - Arguments.of(-2789899L, "getLongValue", "long_-2789899", Long.class), - // getDoubleValue - Arguments.of(-888.541D, "getDoubleValue", an_unknown_key, Double.class), - Arguments.of(1234567890.12, "getDoubleValue", "double_1234567890_12", Double.class), - // getValueOrDefault - Arguments.of("jodd", "getValueOrDefault", an_unknown_key, String.class), - Arguments.of("jodd", "getValueOrDefault", "string_jodd", String.class) - ); - } - - } -} diff --git a/jodd-props/src/test/resources/jodd/props/data/i141-2.props b/jodd-props/src/test/resources/jodd/props/data/i141-2.props deleted file mode 100644 index 0f49fc224..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/i141-2.props +++ /dev/null @@ -1,22 +0,0 @@ -[] -code=NOT AN ERROR 1 -label=NOT AN ERROR 2 -details=NOT AN ERROR 3 - -[] -code=#UNDEFINED -label=UNDEFINED LABEL -details=UNDEFINED DETAILS - -# Error ONE -[] -code=#ONE -label=THIS IS ERROR #ONE - -# Error TWO -[] -#Just like "ERROR.ONE" profile -code=#TWO -#:Trying to set inner profile on section... -[] -label=THIS IS ERROR #TWO \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/i141.props b/jodd-props/src/test/resources/jodd/props/data/i141.props deleted file mode 100644 index 3a9c6b099..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/i141.props +++ /dev/null @@ -1,7 +0,0 @@ -[] -key1=value1 -key2=value2 - -[] -key1=value1#ONE -key2=value2#ONE diff --git a/jodd-props/src/test/resources/jodd/props/data/test-actp.props b/jodd-props/src/test/resources/jodd/props/data/test-actp.props deleted file mode 100644 index 35c1b880b..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test-actp.props +++ /dev/null @@ -1,7 +0,0 @@ - -key1=hello -key1=Hi! -key1=Hola! -key2=world - -@profiles=one.two \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test-all.props b/jodd-props/src/test/resources/jodd/props/data/test-all.props deleted file mode 100644 index 9c6d2187e..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test-all.props +++ /dev/null @@ -1,13 +0,0 @@ - -# this is a comment -; another form of comment - -key1 = value1 -key2 : value2 - -[section1] -key.with.macro = val_${key1} - -[] -key = value A -key = value B diff --git a/jodd-props/src/test/resources/jodd/props/data/test-dupl.props b/jodd-props/src/test/resources/jodd/props/data/test-dupl.props deleted file mode 100644 index 9e0d81efd..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test-dupl.props +++ /dev/null @@ -1,7 +0,0 @@ -foo=one -foo=two -foo:three - -bar=here -bar=there -bar=everywhere \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test-e.props b/jodd-props/src/test/resources/jodd/props/data/test-e.props deleted file mode 100644 index 1b6525460..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test-e.props +++ /dev/null @@ -1,9 +0,0 @@ -# test empty properties - -ok=good - -weird - -empty= - -null \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test-profiles.props b/jodd-props/src/test/resources/jodd/props/data/test-profiles.props deleted file mode 100644 index d41f83deb..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test-profiles.props +++ /dev/null @@ -1,21 +0,0 @@ -foo=one -bar=two -bar=four -bar=ten - -vitamine=12345 - -[db] -url=localhost -username=root - -[db] -url=192.168.1.102 -username=user - -[] -key1=hello -key1=Hi! -key1=Hola! -key2=world -key3=Grazias \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test.properties b/jodd-props/src/test/resources/jodd/props/data/test.properties deleted file mode 100644 index 302022c52..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test.properties +++ /dev/null @@ -1,8 +0,0 @@ -# standard java properties file - -one=value - -two=long value\ - in two lines - -three=some utf8 \u0161\u0111\u017e\u010d\u0107 \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test.props b/jodd-props/src/test/resources/jodd/props/data/test.props deleted file mode 100644 index 14bd2f2a8..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test.props +++ /dev/null @@ -1,36 +0,0 @@ -# Global properties -story = Snow White and the Seven Dwarfs -year = 1937{c} -url = www.imdb.com/title/tt0029583/ -; A backslash at the end of the line escapes the new line -; character, the property value continues to the next line. -; Since the ; character (after Dwarfs) starts a comment -; (either at the beginning or middle of the line, we have -; to escape it with a backslash. -plot = Snow White, pursued by a jealous queen, hides with the Dwarfs; \ - the queen feeds her a poison apple, but Prince Charming \ - awakens her with a kiss. - -# This is also a comment line -# The first : can also be used as assignment operator -Tagline: Walt Disney's New characters in his first full-length production! -file = C:\\local\\snowwhite.mpg - -; Bashful -[bashful] -weight = 45.7 -height = 98.8 -age = 67 -homePage = http://snowwhite.tale/~bashful - -; Doc -[doc] -weight = 49.5 -height = 87.7 -age = 63 -homePage = http://doc.dwarfs - -[] -comment=Čađavi Žar utf8 -special-chars=foo\tboo\rzoo\nxxx\ftoo -special2=\\\\a diff --git a/jodd-props/src/test/resources/jodd/props/data/test2.props b/jodd-props/src/test/resources/jodd/props/data/test2.props deleted file mode 100644 index 20d2ac372..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test2.props +++ /dev/null @@ -1,14 +0,0 @@ - -data.path=${root}/data - -root=/app - -data.path<@prof1>=${root}/data2 - -data.path<@prof2>=${root}/data3 -root<@prof2>=/foo - -data.path<@p1><@p2>=${root}/re -root<@p1>=/roo - -data.mypath=${root<@p1>}/mypath \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/data/test3.props b/jodd-props/src/test/resources/jodd/props/data/test3.props deleted file mode 100644 index fd80c3995..000000000 --- a/jodd-props/src/test/resources/jodd/props/data/test3.props +++ /dev/null @@ -1,15 +0,0 @@ - -email.from=info@jodd.org;patrick@jodd.org - -email.subject=[ERROR] Got %s exceptions - -email.text=line1\ - line2\ -line3 - -email.footer=''' - Hello from - the multiline - value -''' -email.header=aaa \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/moreProfilePropertiesThanBase.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/moreProfilePropertiesThanBase.props deleted file mode 100644 index 388eb5a3f..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/moreProfilePropertiesThanBase.props +++ /dev/null @@ -1,3 +0,0 @@ -myOneProperty=and it's value -mySecondProperty=I've got production written all over me -mySecondProperty=TEST TEST TEST!! \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/multilineValue.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/multilineValue.props deleted file mode 100644 index 74795e0cf..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/multilineValue.props +++ /dev/null @@ -1,2 +0,0 @@ -myOneProperty=long value\ -in two lines \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/oneProfile.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/oneProfile.props deleted file mode 100644 index eeab276d3..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/oneProfile.props +++ /dev/null @@ -1,2 +0,0 @@ -myOneProperty=and it's value -myOneProperty=I've got production written all over me \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/singleProperty.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/singleProperty.props deleted file mode 100644 index 80e417685..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/singleProperty.props +++ /dev/null @@ -1 +0,0 @@ -myOneProperty=and it's value \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/twoProfiles.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/twoProfiles.props deleted file mode 100644 index 8c80d378a..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/twoProfiles.props +++ /dev/null @@ -1,3 +0,0 @@ -myOneProperty=and it's value -myOneProperty=I've got production written all over me -myOneProperty=TEST TEST TEST!! \ No newline at end of file diff --git a/jodd-props/src/test/resources/jodd/props/propsutil/convert/utf8Value.props b/jodd-props/src/test/resources/jodd/props/propsutil/convert/utf8Value.props deleted file mode 100644 index 52756bb95..000000000 --- a/jodd-props/src/test/resources/jodd/props/propsutil/convert/utf8Value.props +++ /dev/null @@ -1 +0,0 @@ -myOneProperty=some utf8 \u0161\u0111\u017e\u010d\u0107 \ No newline at end of file diff --git a/jodd-servlet/build.gradle b/jodd-servlet/build.gradle index 6de84b3c6..d51aa9f8a 100644 --- a/jodd-servlet/build.gradle +++ b/jodd-servlet/build.gradle @@ -4,7 +4,8 @@ ext.moduleDescription = 'Jodd Servlet is set of web tools, including the nice ta dependencies { api project(':jodd-core') - api 'org.jodd:jodd-lagarto:6.0.1' + api 'org.jodd:jodd-lagarto:6.0.2' + api 'org.jodd:jodd-http:6.0.2' provided lib.servlet provided lib.jsp @@ -14,6 +15,5 @@ dependencies { testImplementation lib.el_api testImplementation lib.tomcat_embed - testImplementation project(':jodd-http') } diff --git a/jodd-servlet/src/main/java/jodd/servlet/ServletUtil.java b/jodd-servlet/src/main/java/jodd/servlet/ServletUtil.java index 118ba7244..c4d3991e9 100644 --- a/jodd-servlet/src/main/java/jodd/servlet/ServletUtil.java +++ b/jodd-servlet/src/main/java/jodd/servlet/ServletUtil.java @@ -26,9 +26,9 @@ package jodd.servlet; import jodd.core.JoddCore; +import jodd.http.upload.FileUpload; import jodd.io.FileNameUtil; import jodd.io.IOUtil; -import jodd.io.upload.FileUpload; import jodd.net.MimeTypes; import jodd.net.URLCoder; import jodd.servlet.upload.MultipartRequest; diff --git a/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequest.java b/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequest.java index e668eda53..b805c6d66 100644 --- a/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequest.java +++ b/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequest.java @@ -26,8 +26,8 @@ package jodd.servlet.upload; import jodd.core.JoddCore; -import jodd.io.upload.FileUploadFactory; -import jodd.io.upload.MultipartStreamParser; +import jodd.http.upload.FileUploadFactory; +import jodd.http.upload.MultipartStreamParser; import jodd.servlet.ServletUtil; import javax.servlet.http.HttpServletRequest; @@ -60,7 +60,7 @@ public class MultipartRequest extends MultipartStreamParser { // ---------------------------------------------------------------- properties - private HttpServletRequest request; + private final HttpServletRequest request; private String characterEncoding; /** @@ -164,14 +164,14 @@ public void parseRequest() throws IOException { if (ServletUtil.isMultipartRequest(request)) { parseRequestStream(request.getInputStream(), characterEncoding); } else { - Enumeration names = request.getParameterNames(); + final Enumeration names = request.getParameterNames(); while (names.hasMoreElements()) { - String paramName = (String) names.nextElement(); - String[] values = request.getParameterValues(paramName); + final String paramName = (String) names.nextElement(); + final String[] values = request.getParameterValues(paramName); putParameters(paramName, values); } } } -} \ No newline at end of file +} diff --git a/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequestWrapper.java b/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequestWrapper.java index 3e2fc11dd..96b537285 100644 --- a/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequestWrapper.java +++ b/jodd-servlet/src/main/java/jodd/servlet/upload/MultipartRequestWrapper.java @@ -25,8 +25,8 @@ package jodd.servlet.upload; -import jodd.io.upload.FileUpload; -import jodd.io.upload.FileUploadFactory; +import jodd.http.upload.FileUpload; +import jodd.http.upload.FileUploadFactory; import jodd.servlet.ServletUtil; import javax.servlet.http.HttpServletRequest; @@ -135,10 +135,10 @@ public Map getParameterMap() { if (mreq == null) { return super.getParameterMap(); } - Map map = new HashMap<>(); - Enumeration enumeration = getParameterNames(); + final Map map = new HashMap<>(); + final Enumeration enumeration = getParameterNames(); while (enumeration.hasMoreElements()) { - String name = (String) enumeration.nextElement(); + final String name = (String) enumeration.nextElement(); map.put(name, this.getParameterValues(name)); } return map; diff --git a/settings.gradle b/settings.gradle index b209b0062..253aa480b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,12 +2,10 @@ include 'jodd-core' include 'jodd-db' include 'jodd-decora' include 'jodd-htmlstapler' -include 'jodd-http' include 'jodd-joy' include 'jodd-jtx' include 'jodd-madvoc' include 'jodd-petite' -include 'jodd-props' include 'jodd-proxetta' include 'jodd-servlet' include 'jodd-vtor'