Merge remote-tracking branch 'origin/master' into jetty-9.1
Conflicts: jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
This commit is contained in:
commit
1e25778d57
|
@ -68,6 +68,7 @@ public class HttpField
|
|||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
|
||||
CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
|
||||
|
||||
// Content types
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
||||
|
@ -64,11 +63,18 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* per parser dynamic Trie of {@link HttpFields} from previous parsed messages
|
||||
* is used to help the parsing of subsequent messages.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
|
||||
* then the parser will strictly pass on the exact strings received for methods and header
|
||||
* fields. Otherwise a fast case insensitive string lookup is used that may alter the
|
||||
* case of the method and/or headers
|
||||
* </p>
|
||||
*/
|
||||
public class HttpParser
|
||||
{
|
||||
public static final Logger LOG = Log.getLogger(HttpParser.class);
|
||||
static final int INITIAL_URI_LENGTH=256;
|
||||
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
|
||||
public final static int INITIAL_URI_LENGTH=256;
|
||||
|
||||
// States
|
||||
public enum State
|
||||
|
@ -83,7 +89,6 @@ public class HttpParser
|
|||
REQUEST_VERSION,
|
||||
REASON,
|
||||
HEADER,
|
||||
HEADER_NAME,
|
||||
HEADER_IN_NAME,
|
||||
HEADER_VALUE,
|
||||
HEADER_IN_VALUE,
|
||||
|
@ -102,6 +107,7 @@ public class HttpParser
|
|||
private final RequestHandler<ByteBuffer> _requestHandler;
|
||||
private final ResponseHandler<ByteBuffer> _responseHandler;
|
||||
private final int _maxHeaderBytes;
|
||||
private final boolean _strict;
|
||||
private HttpField _field;
|
||||
private HttpHeader _header;
|
||||
private String _headerString;
|
||||
|
@ -135,31 +141,45 @@ public class HttpParser
|
|||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(RequestHandler<ByteBuffer> handler)
|
||||
{
|
||||
this(handler,-1);
|
||||
this(handler,-1,__STRICT);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(ResponseHandler<ByteBuffer> handler)
|
||||
{
|
||||
this(handler,-1);
|
||||
this(handler,-1,__STRICT);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
|
||||
{
|
||||
_handler=handler;
|
||||
_requestHandler=handler;
|
||||
_responseHandler=null;
|
||||
_maxHeaderBytes=maxHeaderBytes;
|
||||
this(handler,maxHeaderBytes,__STRICT);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
|
||||
{
|
||||
this(handler,maxHeaderBytes,__STRICT);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
|
||||
{
|
||||
_handler=handler;
|
||||
_requestHandler=handler;
|
||||
_responseHandler=null;
|
||||
_maxHeaderBytes=maxHeaderBytes;
|
||||
_strict=strict;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
|
||||
{
|
||||
_handler=handler;
|
||||
_requestHandler=null;
|
||||
_responseHandler=handler;
|
||||
_maxHeaderBytes=maxHeaderBytes;
|
||||
_strict=strict;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
@ -357,15 +377,15 @@ public class HttpParser
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private String takeString()
|
||||
private void setString(String s)
|
||||
{
|
||||
String s =_string.toString();
|
||||
_string.setLength(0);
|
||||
return s;
|
||||
_string.append(s);
|
||||
_length=s.length();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
private String takeLengthString()
|
||||
private String takeString()
|
||||
{
|
||||
_string.setLength(_length);
|
||||
String s =_string.toString();
|
||||
|
@ -411,9 +431,10 @@ public class HttpParser
|
|||
case METHOD:
|
||||
if (ch == HttpTokens.SPACE)
|
||||
{
|
||||
_length=_string.length();
|
||||
_methodString=takeString();
|
||||
HttpMethod method=HttpMethod.CACHE.get(_methodString);
|
||||
if (method!=null)
|
||||
if (method!=null && !_strict)
|
||||
_methodString=method.asString();
|
||||
setState(State.SPACE1);
|
||||
}
|
||||
|
@ -426,6 +447,7 @@ public class HttpParser
|
|||
case RESPONSE_VERSION:
|
||||
if (ch == HttpTokens.SPACE)
|
||||
{
|
||||
_length=_string.length();
|
||||
String version=takeString();
|
||||
_version=HttpVersion.CACHE.get(version);
|
||||
if (_version==null)
|
||||
|
@ -605,7 +627,10 @@ public class HttpParser
|
|||
if (ch == HttpTokens.LINE_FEED)
|
||||
{
|
||||
if (_version==null)
|
||||
{
|
||||
_length=_string.length();
|
||||
_version=HttpVersion.CACHE.get(takeString());
|
||||
}
|
||||
if (_version==null)
|
||||
throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
|
||||
|
||||
|
@ -632,7 +657,7 @@ public class HttpParser
|
|||
case REASON:
|
||||
if (ch == HttpTokens.LINE_FEED)
|
||||
{
|
||||
String reason=takeLengthString();
|
||||
String reason=takeString();
|
||||
|
||||
setState(State.HEADER);
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
|
||||
|
@ -810,14 +835,18 @@ public class HttpParser
|
|||
case HttpTokens.TAB:
|
||||
{
|
||||
// header value without name - continuation?
|
||||
_string.setLength(0);
|
||||
if (_valueString!=null)
|
||||
if (_valueString==null)
|
||||
{
|
||||
_string.append(_valueString);
|
||||
_string.append(' ');
|
||||
_string.setLength(0);
|
||||
_length=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
setString(_valueString);
|
||||
_string.append(' ');
|
||||
_length++;
|
||||
_valueString=null;
|
||||
}
|
||||
_length=_string.length();
|
||||
_valueString=null;
|
||||
setState(State.HEADER_VALUE);
|
||||
break;
|
||||
}
|
||||
|
@ -913,14 +942,35 @@ public class HttpParser
|
|||
|
||||
if (field!=null)
|
||||
{
|
||||
String n=field.getName();
|
||||
String v=field.getValue();
|
||||
final String n;
|
||||
final String v;
|
||||
|
||||
if (_strict)
|
||||
{
|
||||
// Have to get the fields exactly from the buffer to match case
|
||||
String fn=field.getName();
|
||||
String fv=field.getValue();
|
||||
n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StringUtil.__US_ASCII_CHARSET);
|
||||
if (fv==null)
|
||||
v=null;
|
||||
else
|
||||
{
|
||||
v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StringUtil.__ISO_8859_1_CHARSET);
|
||||
field=new HttpField(field.getHeader(),n,v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
n=field.getName();
|
||||
v=field.getValue();
|
||||
}
|
||||
|
||||
_header=field.getHeader();
|
||||
_headerString=n;
|
||||
|
||||
if (v==null)
|
||||
{
|
||||
// Header only
|
||||
_header=field.getHeader();
|
||||
_headerString=n;
|
||||
setState(State.HEADER_VALUE);
|
||||
_string.setLength(0);
|
||||
_length=0;
|
||||
|
@ -936,8 +986,6 @@ public class HttpParser
|
|||
if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
|
||||
{
|
||||
_field=field;
|
||||
_header=_field.getHeader();
|
||||
_headerString=n;
|
||||
_valueString=v;
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
|
||||
|
@ -950,190 +998,111 @@ public class HttpParser
|
|||
buffer.position(pos);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
setString(v);
|
||||
buffer.position(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New header
|
||||
setState(State.HEADER_NAME);
|
||||
setState(State.HEADER_IN_NAME);
|
||||
_string.setLength(0);
|
||||
_string.append((char)ch);
|
||||
_length=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HEADER_NAME:
|
||||
if (ch<0)
|
||||
throw new BadMessage();
|
||||
switch(ch)
|
||||
{
|
||||
case HttpTokens.LINE_FEED:
|
||||
if (_headerString==null)
|
||||
{
|
||||
_headerString=takeLengthString();
|
||||
_header=HttpHeader.CACHE.get(_headerString);
|
||||
}
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
|
||||
case HttpTokens.COLON:
|
||||
if (_headerString==null)
|
||||
{
|
||||
_headerString=takeLengthString();
|
||||
_header=HttpHeader.CACHE.get(_headerString);
|
||||
}
|
||||
setState(State.HEADER_VALUE);
|
||||
break;
|
||||
|
||||
case HttpTokens.SPACE:
|
||||
case HttpTokens.TAB:
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
_string.append((char)ch);
|
||||
_length=_string.length();
|
||||
setState(State.HEADER_IN_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case HEADER_IN_NAME:
|
||||
if (ch<0)
|
||||
throw new BadMessage("Illegal character");
|
||||
switch(ch)
|
||||
if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
|
||||
{
|
||||
case HttpTokens.LINE_FEED:
|
||||
if (_headerString==null)
|
||||
{
|
||||
_headerString=takeString();
|
||||
_length=-1;
|
||||
_header=HttpHeader.CACHE.get(_headerString);
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
}
|
||||
_length=-1;
|
||||
|
||||
case HttpTokens.COLON:
|
||||
if (_headerString==null)
|
||||
{
|
||||
_headerString=takeString();
|
||||
_header=HttpHeader.CACHE.get(_headerString);
|
||||
}
|
||||
_length=-1;
|
||||
setState(State.HEADER_VALUE);
|
||||
break;
|
||||
case HttpTokens.SPACE:
|
||||
case HttpTokens.TAB:
|
||||
if (_header!=null)
|
||||
{
|
||||
_string.setLength(0);
|
||||
_string.append(_header.asString());
|
||||
_length=_string.length();
|
||||
_header=null;
|
||||
_headerString=null;
|
||||
}
|
||||
setState(State.HEADER_NAME);
|
||||
_string.append((char)ch);
|
||||
break;
|
||||
default:
|
||||
if (_header!=null)
|
||||
{
|
||||
_string.setLength(0);
|
||||
_string.append(_header.asString());
|
||||
_length=_string.length();
|
||||
_header=null;
|
||||
_headerString=null;
|
||||
}
|
||||
_string.append((char)ch);
|
||||
_length++;
|
||||
setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
|
||||
{
|
||||
if (_header!=null)
|
||||
{
|
||||
setString(_header.asString());
|
||||
_header=null;
|
||||
_headerString=null;
|
||||
}
|
||||
|
||||
_string.append((char)ch);
|
||||
if (ch>HttpTokens.SPACE)
|
||||
_length=_string.length();
|
||||
break;
|
||||
}
|
||||
|
||||
throw new BadMessage("Illegal character");
|
||||
|
||||
case HEADER_VALUE:
|
||||
switch(ch)
|
||||
if (ch>HttpTokens.SPACE || ch<0)
|
||||
{
|
||||
case HttpTokens.LINE_FEED:
|
||||
if (_length > 0)
|
||||
{
|
||||
if (_valueString!=null)
|
||||
{
|
||||
// multi line value!
|
||||
_value=null;
|
||||
_valueString+=" "+takeLengthString();
|
||||
}
|
||||
else if (HttpHeaderValue.hasKnownValues(_header))
|
||||
{
|
||||
_valueString=takeLengthString();
|
||||
_value=HttpHeaderValue.CACHE.get(_valueString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_value=null;
|
||||
_valueString=takeLengthString();
|
||||
}
|
||||
}
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
case HttpTokens.SPACE:
|
||||
case HttpTokens.TAB:
|
||||
break;
|
||||
default:
|
||||
{
|
||||
_string.append((char)(0xff&ch));
|
||||
_length=_string.length();
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
}
|
||||
_string.append((char)(0xff&ch));
|
||||
_length=_string.length();
|
||||
setState(State.HEADER_IN_VALUE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
|
||||
break;
|
||||
|
||||
if (ch==HttpTokens.LINE_FEED)
|
||||
{
|
||||
if (_length > 0)
|
||||
{
|
||||
_value=null;
|
||||
_valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
|
||||
}
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
}
|
||||
|
||||
throw new BadMessage("Illegal character");
|
||||
|
||||
case HEADER_IN_VALUE:
|
||||
switch(ch)
|
||||
if (ch>=HttpTokens.SPACE || ch<0)
|
||||
{
|
||||
case HttpTokens.LINE_FEED:
|
||||
if (_length > 0)
|
||||
{
|
||||
if (HttpHeaderValue.hasKnownValues(_header))
|
||||
{
|
||||
_valueString=takeString();
|
||||
_value=HttpHeaderValue.CACHE.get(_valueString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_value=null;
|
||||
_valueString=takeString();
|
||||
}
|
||||
_length=-1;
|
||||
}
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
case HttpTokens.SPACE:
|
||||
case HttpTokens.TAB:
|
||||
if (_valueString!=null)
|
||||
{
|
||||
_string.setLength(0);
|
||||
_string.append(_valueString);
|
||||
_length=_valueString.length();
|
||||
_valueString=null;
|
||||
_field=null;
|
||||
}
|
||||
_string.append((char)ch);
|
||||
setState(State.HEADER_VALUE);
|
||||
break;
|
||||
default:
|
||||
if (_valueString!=null)
|
||||
{
|
||||
_string.setLength(0);
|
||||
_string.append(_valueString);
|
||||
_length=_valueString.length();
|
||||
_valueString=null;
|
||||
_field=null;
|
||||
}
|
||||
_string.append((char)(0xff&ch));
|
||||
_length++;
|
||||
if (_valueString!=null)
|
||||
{
|
||||
setString(_valueString);
|
||||
_valueString=null;
|
||||
_field=null;
|
||||
}
|
||||
_string.append((char)(0xff&ch));
|
||||
if (ch>HttpTokens.SPACE || ch<0)
|
||||
_length=_string.length();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
if (ch==HttpTokens.LINE_FEED)
|
||||
{
|
||||
if (_length > 0)
|
||||
{
|
||||
_value=null;
|
||||
_valueString=takeString();
|
||||
_length=-1;
|
||||
}
|
||||
setState(State.HEADER);
|
||||
break;
|
||||
}
|
||||
throw new BadMessage("Illegal character");
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(_state.toString());
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ import org.eclipse.jetty.http.HttpParser.State;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -404,6 +403,50 @@ public class HttpParserTest
|
|||
HttpParser parser= new HttpParser(handler);
|
||||
parseAll(parser,buffer);
|
||||
assertThat(_bad,Matchers.notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonStrict() throws Exception
|
||||
{
|
||||
ByteBuffer buffer= BufferUtil.toBuffer(
|
||||
"get / http/1.0\015\012" +
|
||||
"HOST: localhost\015\012" +
|
||||
"cOnNeCtIoN: ClOsE\015\012"+
|
||||
"\015\012");
|
||||
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
|
||||
HttpParser parser= new HttpParser(handler,-1,false);
|
||||
parseAll(parser,buffer);
|
||||
|
||||
assertEquals("GET", _methodOrVersion);
|
||||
assertEquals("/", _uriOrStatus);
|
||||
assertEquals("HTTP/1.0", _versionOrReason);
|
||||
assertEquals("Host", _hdr[0]);
|
||||
assertEquals("localhost", _val[0]);
|
||||
assertEquals("Connection", _hdr[1]);
|
||||
assertEquals("close", _val[1]);
|
||||
assertEquals(1, _headers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrict() throws Exception
|
||||
{
|
||||
ByteBuffer buffer= BufferUtil.toBuffer(
|
||||
"gEt / http/1.0\015\012" +
|
||||
"HOST: localhost\015\012" +
|
||||
"cOnNeCtIoN: ClOsE\015\012"+
|
||||
"\015\012");
|
||||
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
|
||||
HttpParser parser= new HttpParser(handler,-1,true);
|
||||
parseAll(parser,buffer);
|
||||
|
||||
assertEquals("gEt", _methodOrVersion);
|
||||
assertEquals("/", _uriOrStatus);
|
||||
assertEquals("HTTP/1.0", _versionOrReason);
|
||||
assertEquals("HOST", _hdr[0]);
|
||||
assertEquals("localhost", _val[0]);
|
||||
assertEquals("cOnNeCtIoN", _hdr[1]);
|
||||
assertEquals("ClOsE", _val[1]);
|
||||
assertEquals(1, _headers);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -580,7 +623,6 @@ public class HttpParserTest
|
|||
+ "\015\012"
|
||||
+ "0123456789\015\012");
|
||||
|
||||
|
||||
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
|
||||
HttpParser parser= new HttpParser(handler);
|
||||
parser.parseNext(buffer);
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.WriteListener;
|
||||
|
|
|
@ -37,7 +37,6 @@ import java.io.OutputStream;
|
|||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
@ -57,7 +56,6 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.matchers.JUnitMatchers;
|
||||
/**
|
||||
|
|
|
@ -34,7 +34,6 @@ import java.io.InputStream;
|
|||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -60,7 +59,6 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
|
|
@ -53,12 +53,14 @@ public class StringUtil
|
|||
public final static Charset __UTF8_CHARSET;
|
||||
public final static Charset __ISO_8859_1_CHARSET;
|
||||
public final static Charset __UTF16_CHARSET;
|
||||
public final static Charset __US_ASCII_CHARSET;
|
||||
|
||||
static
|
||||
{
|
||||
__UTF8_CHARSET=Charset.forName(__UTF8);
|
||||
__ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
|
||||
__UTF16_CHARSET=Charset.forName(__UTF16);
|
||||
__US_ASCII_CHARSET=Charset.forName("US-ASCII");
|
||||
|
||||
CHARSETS.put("UTF-8",__UTF8);
|
||||
CHARSETS.put("UTF8",__UTF8);
|
||||
|
|
Loading…
Reference in New Issue