mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-21 06:45:39 -06:00
Core: SSL support for Socket, finishing touches...
git-svn-id: svn://ultimatepp.org/upp/trunk@4785 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
8aace300c4
commit
eef7c20270
12 changed files with 476 additions and 394 deletions
|
|
@ -4,8 +4,6 @@ NAMESPACE_UPP
|
||||||
|
|
||||||
#define LTIMING(x) // TIMING(x)
|
#define LTIMING(x) // TIMING(x)
|
||||||
|
|
||||||
int msecs(int from) { return (int)GetTickCount() - from; }
|
|
||||||
|
|
||||||
#ifdef PLATFORM_WIN32
|
#ifdef PLATFORM_WIN32
|
||||||
#include <mmsystem.h>
|
#include <mmsystem.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,6 @@ void CloseStdLog();
|
||||||
|
|
||||||
void HexDump(Stream& s, const void *ptr, int size, int maxsize = INT_MAX);
|
void HexDump(Stream& s, const void *ptr, int size, int maxsize = INT_MAX);
|
||||||
|
|
||||||
int msecs(int from = 0);
|
|
||||||
|
|
||||||
String GetTypeName(const char *type_name);
|
String GetTypeName(const char *type_name);
|
||||||
inline String GetTypeName(const ::std::type_info& tinfo) { return GetTypeName(tinfo.name()); }
|
inline String GetTypeName(const ::std::type_info& tinfo) { return GetTypeName(tinfo.name()); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
String WwwFormat(Time tm);
|
||||||
String FormatIP(dword _ip);
|
String FormatIP(dword _ip);
|
||||||
|
|
||||||
String UrlEncode(const String& s);
|
String UrlEncode(const String& s);
|
||||||
|
|
@ -81,7 +82,7 @@ class TcpSocket {
|
||||||
|
|
||||||
struct SSL {
|
struct SSL {
|
||||||
virtual bool Start() = 0;
|
virtual bool Start() = 0;
|
||||||
virtual bool Wait(dword flags) = 0;
|
virtual bool Wait(dword flags, int end_time) = 0;
|
||||||
virtual int Send(const void *buffer, int maxlen) = 0;
|
virtual int Send(const void *buffer, int maxlen) = 0;
|
||||||
virtual int Recv(void *buffer, int maxlen) = 0;
|
virtual int Recv(void *buffer, int maxlen) = 0;
|
||||||
virtual void Close() = 0;
|
virtual void Close() = 0;
|
||||||
|
|
@ -92,7 +93,9 @@ class TcpSocket {
|
||||||
|
|
||||||
One<SSL> ssl;
|
One<SSL> ssl;
|
||||||
One<SSLInfo> sslinfo;
|
One<SSLInfo> sslinfo;
|
||||||
|
String cert, pkey;
|
||||||
|
bool asn1;
|
||||||
|
|
||||||
struct SSLImp;
|
struct SSLImp;
|
||||||
friend struct SSLImp;
|
friend struct SSLImp;
|
||||||
|
|
||||||
|
|
@ -101,7 +104,9 @@ class TcpSocket {
|
||||||
|
|
||||||
friend void InitCreateSSL();
|
friend void InitCreateSSL();
|
||||||
|
|
||||||
bool RawWait(dword flags);
|
int GetEndTime() const;
|
||||||
|
bool RawWait(dword flags, int end_time);
|
||||||
|
bool Wait(dword events, int end_time);
|
||||||
SOCKET AcceptRaw(dword *ipaddr, int timeout_msec);
|
SOCKET AcceptRaw(dword *ipaddr, int timeout_msec);
|
||||||
bool Open(int family, int type, int protocol);
|
bool Open(int family, int type, int protocol);
|
||||||
int RawRecv(void *buffer, int maxlen);
|
int RawRecv(void *buffer, int maxlen);
|
||||||
|
|
@ -111,9 +116,11 @@ class TcpSocket {
|
||||||
bool RawConnect(addrinfo *info);
|
bool RawConnect(addrinfo *info);
|
||||||
void RawClose();
|
void RawClose();
|
||||||
|
|
||||||
void ReadBuffer();
|
void ReadBuffer(int end_time);
|
||||||
int Get_();
|
int Get_();
|
||||||
int Peek_();
|
int Peek_();
|
||||||
|
int Peek_(int end_time);
|
||||||
|
int Peek(int end_time) { return ptr < end ? *ptr : Peek_(end_time); }
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
|
|
@ -170,18 +177,21 @@ public:
|
||||||
int Get() { return ptr < end ? *ptr++ : Get_(); }
|
int Get() { return ptr < end ? *ptr++ : Get_(); }
|
||||||
int Get(void *buffer, int len);
|
int Get(void *buffer, int len);
|
||||||
String Get(int len);
|
String Get(int len);
|
||||||
int GetAll(void *buffer, int len) { return Get(buffer, len) == len; }
|
|
||||||
String GetAll(int len);
|
|
||||||
String GetLine(int maxlen = 2000000);
|
|
||||||
|
|
||||||
int Put(const char *s, int len);
|
int Put(const char *s, int len);
|
||||||
int Put(const String& s) { return Put(s.Begin(), s.GetLength()); }
|
int Put(const String& s) { return Put(s.Begin(), s.GetLength()); }
|
||||||
bool PutAll(const char *s, int len) { return Put(s, len) == len; }
|
|
||||||
bool PutAll(const String& s) { return Put(s) == s.GetCount(); }
|
bool GetAll(void *buffer, int len);
|
||||||
|
String GetAll(int len);
|
||||||
|
String GetLine(int maxlen = 65536);
|
||||||
|
|
||||||
|
bool PutAll(const char *s, int len);
|
||||||
|
bool PutAll(const String& s);
|
||||||
|
|
||||||
bool StartSSL();
|
bool StartSSL();
|
||||||
bool IsSSL() const { return ssl; }
|
bool IsSSL() const { return ssl; }
|
||||||
bool SSLHandshake();
|
bool SSLHandshake();
|
||||||
|
void SSLCertificate(const String& cert, const String& pkey, bool asn1);
|
||||||
const SSLInfo *GetSSLInfo() const { return ~sslinfo; }
|
const SSLInfo *GetSSLInfo() const { return ~sslinfo; }
|
||||||
|
|
||||||
TcpSocket& Timeout(int ms) { timeout = ms; return *this; }
|
TcpSocket& Timeout(int ms) { timeout = ms; return *this; }
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,19 @@
|
||||||
|
|
||||||
NAMESPACE_UPP
|
NAMESPACE_UPP
|
||||||
|
|
||||||
|
String WwwFormat(Time tm)
|
||||||
|
{
|
||||||
|
static const char *dayofweek[] =
|
||||||
|
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||||
|
static const char *month[] =
|
||||||
|
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||||
|
return String().Cat()
|
||||||
|
<< dayofweek[DayOfWeek(tm)] << ", "
|
||||||
|
<< (int)tm.day << ' ' << month[tm.month - 1]
|
||||||
|
<< ' ' << (int)tm.year
|
||||||
|
<< ' ' << Sprintf("%2d:%02d:%02d +0100", tm.hour, tm.minute, tm.second);
|
||||||
|
}
|
||||||
|
|
||||||
String FormatIP(dword _ip)
|
String FormatIP(dword _ip)
|
||||||
{
|
{
|
||||||
byte ip[4];
|
byte ip[4];
|
||||||
|
|
|
||||||
|
|
@ -1,136 +1,136 @@
|
||||||
#include <Core/Core.h>
|
#include <Core/Core.h>
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/engine.h>
|
#include <openssl/engine.h>
|
||||||
|
|
||||||
NAMESPACE_UPP
|
NAMESPACE_UPP
|
||||||
|
|
||||||
void SslInitThread();
|
void SslInitThread();
|
||||||
|
|
||||||
class SslBuffer
|
class SslBuffer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SslBuffer(BUF_MEM *m = NULL) : buf_mem(m) {}
|
SslBuffer(BUF_MEM *m = NULL) : buf_mem(m) {}
|
||||||
~SslBuffer() { Clear(); }
|
~SslBuffer() { Clear(); }
|
||||||
|
|
||||||
bool IsEmpty() const { return !buf_mem; }
|
bool IsEmpty() const { return !buf_mem; }
|
||||||
|
|
||||||
bool Set(BUF_MEM *b) { Clear(); return !!(buf_mem = b); }
|
bool Set(BUF_MEM *b) { Clear(); return !!(buf_mem = b); }
|
||||||
bool Create() { return Set(BUF_MEM_new()); }
|
bool Create() { return Set(BUF_MEM_new()); }
|
||||||
void Clear() { if(buf_mem) { BUF_MEM_free(buf_mem); buf_mem = NULL; } }
|
void Clear() { if(buf_mem) { BUF_MEM_free(buf_mem); buf_mem = NULL; } }
|
||||||
BUF_MEM *Detach() { BUF_MEM *b = buf_mem; buf_mem = NULL; return b; }
|
BUF_MEM *Detach() { BUF_MEM *b = buf_mem; buf_mem = NULL; return b; }
|
||||||
|
|
||||||
bool Grow(int length);
|
bool Grow(int length);
|
||||||
|
|
||||||
String Get() const;
|
String Get() const;
|
||||||
bool Set(const String& d);
|
bool Set(const String& d);
|
||||||
|
|
||||||
operator BUF_MEM * () const { return buf_mem; }
|
operator BUF_MEM * () const { return buf_mem; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BUF_MEM *buf_mem;
|
BUF_MEM *buf_mem;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SslStream
|
class SslStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SslStream(BIO *b = NULL) : bio(b) {}
|
SslStream(BIO *b = NULL) : bio(b) {}
|
||||||
~SslStream() { Clear(); }
|
~SslStream() { Clear(); }
|
||||||
|
|
||||||
bool IsEmpty() const { return !bio; }
|
bool IsEmpty() const { return !bio; }
|
||||||
|
|
||||||
bool Set(BIO *b) { Clear(); return !!(bio = b); }
|
bool Set(BIO *b) { Clear(); return !!(bio = b); }
|
||||||
bool Create(BIO_METHOD *meth) { return Set(BIO_new(meth)); }
|
bool Create(BIO_METHOD *meth) { return Set(BIO_new(meth)); }
|
||||||
void Clear() { if(bio) { BIO_free(bio); bio = NULL; } }
|
void Clear() { if(bio) { BIO_free(bio); bio = NULL; } }
|
||||||
|
|
||||||
bool OpenBuffer(const char *data, int length);
|
bool OpenBuffer(const char *data, int length);
|
||||||
bool CreateBuffer();
|
bool CreateBuffer();
|
||||||
String GetResult() const;
|
String GetResult() const;
|
||||||
|
|
||||||
operator BIO * () const { return bio; }
|
operator BIO * () const { return bio; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BIO *bio;
|
BIO *bio;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SslKey
|
class SslKey
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SslKey(EVP_PKEY *k = NULL) : key(k) {}
|
SslKey(EVP_PKEY *k = NULL) : key(k) {}
|
||||||
~SslKey() { Clear(); }
|
~SslKey() { Clear(); }
|
||||||
|
|
||||||
bool IsEmpty() const { return !key; }
|
bool IsEmpty() const { return !key; }
|
||||||
|
|
||||||
bool Set(EVP_PKEY *k) { Clear(); return !!(key = k); }
|
bool Set(EVP_PKEY *k) { Clear(); return !!(key = k); }
|
||||||
void Clear() { if(key) { EVP_PKEY_free(key); key = NULL; } }
|
void Clear() { if(key) { EVP_PKEY_free(key); key = NULL; } }
|
||||||
EVP_PKEY *Detach() { EVP_PKEY *k = key; key = NULL; return k; }
|
EVP_PKEY *Detach() { EVP_PKEY *k = key; key = NULL; return k; }
|
||||||
|
|
||||||
operator EVP_PKEY * () const { return key; }
|
operator EVP_PKEY * () const { return key; }
|
||||||
|
|
||||||
bool Load(const String& data);
|
bool Load(const String& data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EVP_PKEY *key;
|
EVP_PKEY *key;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SslCertificate
|
class SslCertificate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SslCertificate(X509 *c = NULL) : cert(c) {}
|
SslCertificate(X509 *c = NULL) : cert(c) {}
|
||||||
~SslCertificate() { Clear(); }
|
~SslCertificate() { Clear(); }
|
||||||
|
|
||||||
bool IsEmpty() const { return !cert; }
|
bool IsEmpty() const { return !cert; }
|
||||||
|
|
||||||
bool Set(X509 *c) { Clear(); return !!(cert = c); }
|
bool Set(X509 *c) { Clear(); return !!(cert = c); }
|
||||||
bool Create() { return Set(X509_new()); }
|
bool Create() { return Set(X509_new()); }
|
||||||
void Clear() { if(cert) { X509_free(cert); cert = NULL; } }
|
void Clear() { if(cert) { X509_free(cert); cert = NULL; } }
|
||||||
X509 *Detach() { X509 *c = cert; cert = NULL; return c; }
|
X509 *Detach() { X509 *c = cert; cert = NULL; return c; }
|
||||||
|
|
||||||
bool Load(const String& data, bool asn1 = false);
|
bool Load(const String& data, bool asn1 = false);
|
||||||
String Save(bool asn1 = false) const;
|
String Save(bool asn1 = false) const;
|
||||||
|
|
||||||
String GetSubjectName() const;
|
String GetSubjectName() const;
|
||||||
String GetIssuerName() const;
|
String GetIssuerName() const;
|
||||||
Date GetNotBefore() const;
|
Date GetNotBefore() const;
|
||||||
Date GetNotAfter() const;
|
Date GetNotAfter() const;
|
||||||
int GetVersion() const;
|
int GetVersion() const;
|
||||||
String GetSerialNumber() const;
|
String GetSerialNumber() const;
|
||||||
|
|
||||||
operator X509 * () const { return cert; }
|
operator X509 * () const { return cert; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
X509 *cert;
|
X509 *cert;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SslContext
|
class SslContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SslContext(SSL_CTX *c = NULL);
|
SslContext(SSL_CTX *c = NULL);
|
||||||
~SslContext() { Clear(); }
|
~SslContext() { Clear(); }
|
||||||
|
|
||||||
bool IsEmpty() const { return !ssl_ctx; }
|
bool IsEmpty() const { return !ssl_ctx; }
|
||||||
|
|
||||||
bool Set(SSL_CTX *c) { Clear(); return !!(ssl_ctx = c); }
|
bool Set(SSL_CTX *c) { Clear(); return !!(ssl_ctx = c); }
|
||||||
bool Create(SSL_METHOD *meth) { return Set(SSL_CTX_new(meth)); }
|
bool Create(SSL_METHOD *meth) { return Set(SSL_CTX_new(meth)); }
|
||||||
void Clear() { if(ssl_ctx) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } }
|
void Clear() { if(ssl_ctx) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } }
|
||||||
SSL_CTX *Detach() { SSL_CTX *c = ssl_ctx; ssl_ctx = NULL; return c; }
|
SSL_CTX *Detach() { SSL_CTX *c = ssl_ctx; ssl_ctx = NULL; return c; }
|
||||||
|
|
||||||
operator SSL_CTX * () const { return ssl_ctx; }
|
operator SSL_CTX * () const { return ssl_ctx; }
|
||||||
|
|
||||||
bool CipherList(const char *list);
|
bool CipherList(const char *list);
|
||||||
bool UseCertificate(String certificate, String private_key, bool cert_asn1 = false);
|
bool UseCertificate(String certificate, String private_key, bool cert_asn1 = false);
|
||||||
void VerifyPeer(bool verify = true, int depth = 2);
|
void VerifyPeer(bool verify = true, int depth = 2);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SSL_CTX *ssl_ctx;
|
SSL_CTX *ssl_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
String SslGetLastError(int& code);
|
String SslGetLastError(int& code);
|
||||||
String SslGetLastError();
|
String SslGetLastError();
|
||||||
String SslToString(X509_NAME *name);
|
String SslToString(X509_NAME *name);
|
||||||
Date ASN1ToDate(ASN1_STRING *time);
|
Date Asn1ToDate(ASN1_STRING *time);
|
||||||
String ASN1ToString(ASN1_STRING *s);
|
String Asn1ToString(ASN1_STRING *s);
|
||||||
|
|
||||||
END_UPP_NAMESPACE
|
END_UPP_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ NAMESPACE_UPP
|
||||||
|
|
||||||
struct TcpSocket::SSLImp : TcpSocket::SSL {
|
struct TcpSocket::SSLImp : TcpSocket::SSL {
|
||||||
virtual bool Start();
|
virtual bool Start();
|
||||||
virtual bool Wait(dword flags);
|
virtual bool Wait(dword flags, int end_time);
|
||||||
virtual int Send(const void *buffer, int maxlen);
|
virtual int Send(const void *buffer, int maxlen);
|
||||||
virtual int Recv(void *buffer, int maxlen);
|
virtual int Recv(void *buffer, int maxlen);
|
||||||
virtual void Close();
|
virtual void Close();
|
||||||
|
|
@ -109,7 +109,8 @@ bool TcpSocket::SSLImp::Start()
|
||||||
SetSSLError("Start: SSL context.");
|
SetSSLError("Start: SSL context.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// context.VerifyPeer();
|
if(socket.cert.GetCount())
|
||||||
|
context.UseCertificate(socket.cert, socket.pkey, socket.asn1);
|
||||||
if(!(ssl = SSL_new(context))) {
|
if(!(ssl = SSL_new(context))) {
|
||||||
SetSSLError("Start: SSL_new");
|
SetSSLError("Start: SSL_new");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -157,12 +158,12 @@ dword TcpSocket::SSLImp::Handshake()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TcpSocket::SSLImp::Wait(dword flags)
|
bool TcpSocket::SSLImp::Wait(dword flags, int end_time)
|
||||||
{
|
{
|
||||||
LLOG("SSL Wait");
|
LLOG("SSL Wait");
|
||||||
if((flags & WAIT_READ) && SSL_pending(ssl) > 0)
|
if((flags & WAIT_READ) && SSL_pending(ssl) > 0)
|
||||||
return true;
|
return true;
|
||||||
return socket.RawWait(flags);
|
return socket.RawWait(flags, end_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
int TcpSocket::SSLImp::Send(const void *buffer, int maxlen)
|
int TcpSocket::SSLImp::Send(const void *buffer, int maxlen)
|
||||||
|
|
|
||||||
|
|
@ -1,208 +1,208 @@
|
||||||
#include "SSL.h"
|
#include "SSL.h"
|
||||||
|
|
||||||
NAMESPACE_UPP
|
NAMESPACE_UPP
|
||||||
|
|
||||||
String SslBuffer::Get() const
|
String SslBuffer::Get() const
|
||||||
{
|
{
|
||||||
if(IsEmpty())
|
if(IsEmpty())
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
return String(buf_mem->data, buf_mem->length);
|
return String(buf_mem->data, buf_mem->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslBuffer::Grow(int length)
|
bool SslBuffer::Grow(int length)
|
||||||
{
|
{
|
||||||
return !IsEmpty() && BUF_MEM_grow(buf_mem, length);
|
return !IsEmpty() && BUF_MEM_grow(buf_mem, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslBuffer::Set(const String& d)
|
bool SslBuffer::Set(const String& d)
|
||||||
{
|
{
|
||||||
if(!buf_mem && !Create())
|
if(!buf_mem && !Create())
|
||||||
return false;
|
return false;
|
||||||
int len = d.GetLength();
|
int len = d.GetLength();
|
||||||
if((int)buf_mem->max < len && !Grow(len))
|
if((int)buf_mem->max < len && !Grow(len))
|
||||||
return false;
|
return false;
|
||||||
ASSERT((int)buf_mem->max >= len);
|
ASSERT((int)buf_mem->max >= len);
|
||||||
buf_mem->length = len;
|
buf_mem->length = len;
|
||||||
memcpy(buf_mem, d, len);
|
memcpy(buf_mem, d, len);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslStream::OpenBuffer(const char *data, int length)
|
bool SslStream::OpenBuffer(const char *data, int length)
|
||||||
{
|
{
|
||||||
return Set(BIO_new_mem_buf(const_cast<char *>(data), length));
|
return Set(BIO_new_mem_buf(const_cast<char *>(data), length));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslStream::CreateBuffer()
|
bool SslStream::CreateBuffer()
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
SslBuffer buf;
|
SslBuffer buf;
|
||||||
if(!buf.Create() || !Create(BIO_s_mem()))
|
if(!buf.Create() || !Create(BIO_s_mem()))
|
||||||
return false;
|
return false;
|
||||||
BIO_set_mem_buf(bio, buf.Detach(), BIO_CLOSE);
|
BIO_set_mem_buf(bio, buf.Detach(), BIO_CLOSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslStream::GetResult() const
|
String SslStream::GetResult() const
|
||||||
{
|
{
|
||||||
if(IsEmpty())
|
if(IsEmpty())
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
BUF_MEM *bm = NULL;
|
BUF_MEM *bm = NULL;
|
||||||
BIO_get_mem_ptr(bio, &bm);
|
BIO_get_mem_ptr(bio, &bm);
|
||||||
if(!bm)
|
if(!bm)
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
return String(bm->data, bm->length);
|
return String(bm->data, bm->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslKey::Load(const String& data)
|
bool SslKey::Load(const String& data)
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
SslStream strm;
|
SslStream strm;
|
||||||
if(!strm.OpenBuffer(data.Begin(), data.GetLength()))
|
if(!strm.OpenBuffer(data.Begin(), data.GetLength()))
|
||||||
return false;
|
return false;
|
||||||
return Set(PEM_read_bio_PrivateKey(strm, NULL, NULL, NULL));
|
return Set(PEM_read_bio_PrivateKey(strm, NULL, NULL, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslCertificate::Load(const String& data, bool asn1)
|
bool SslCertificate::Load(const String& data, bool asn1)
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
SslStream in, pem, *sio = ∈
|
SslStream in, pem, *sio = ∈
|
||||||
if(!in.OpenBuffer(data, data.GetLength()))
|
if(!in.OpenBuffer(data, data.GetLength()))
|
||||||
return false;
|
return false;
|
||||||
if(!asn1)
|
if(!asn1)
|
||||||
{
|
{
|
||||||
if(!pem.Create(BIO_f_base64()))
|
if(!pem.Create(BIO_f_base64()))
|
||||||
return false;
|
return false;
|
||||||
BIO_push(pem, in);
|
BIO_push(pem, in);
|
||||||
sio = &pem;
|
sio = &pem;
|
||||||
}
|
}
|
||||||
return Set(d2i_X509_bio(*sio, NULL));
|
return Set(d2i_X509_bio(*sio, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslCertificate::Save(bool asn1) const
|
String SslCertificate::Save(bool asn1) const
|
||||||
{
|
{
|
||||||
if(IsEmpty())
|
if(IsEmpty())
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
SslStream out, pem, *sio = &out;
|
SslStream out, pem, *sio = &out;
|
||||||
if(!out.CreateBuffer())
|
if(!out.CreateBuffer())
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
if(!asn1)
|
if(!asn1)
|
||||||
{
|
{
|
||||||
if(!pem.Create(BIO_f_base64()))
|
if(!pem.Create(BIO_f_base64()))
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
BIO_push(pem, out);
|
BIO_push(pem, out);
|
||||||
sio = &pem;
|
sio = &pem;
|
||||||
}
|
}
|
||||||
i2d_X509_bio(*sio, cert);
|
i2d_X509_bio(*sio, cert);
|
||||||
return out.GetResult();
|
return out.GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslCertificate::GetSubjectName() const
|
String SslCertificate::GetSubjectName() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return SslToString(X509_get_subject_name(cert));
|
return SslToString(X509_get_subject_name(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslCertificate::GetIssuerName() const
|
String SslCertificate::GetIssuerName() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return SslToString(X509_get_issuer_name(cert));
|
return SslToString(X509_get_issuer_name(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
Date SslCertificate::GetNotBefore() const
|
Date SslCertificate::GetNotBefore() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return ASN1ToDate(X509_get_notBefore(cert));
|
return Asn1ToDate(X509_get_notBefore(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
Date SslCertificate::GetNotAfter() const
|
Date SslCertificate::GetNotAfter() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return ASN1ToDate(X509_get_notAfter(cert));
|
return Asn1ToDate(X509_get_notAfter(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
int SslCertificate::GetVersion() const
|
int SslCertificate::GetVersion() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return X509_get_version(cert);
|
return X509_get_version(cert);
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslCertificate::GetSerialNumber() const
|
String SslCertificate::GetSerialNumber() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsEmpty());
|
ASSERT(!IsEmpty());
|
||||||
return ASN1ToString(X509_get_serialNumber(cert));
|
return Asn1ToString(X509_get_serialNumber(cert));
|
||||||
}
|
}
|
||||||
|
|
||||||
SslContext::SslContext(SSL_CTX *c)
|
SslContext::SslContext(SSL_CTX *c)
|
||||||
: ssl_ctx(c)
|
: ssl_ctx(c)
|
||||||
{
|
{
|
||||||
SslInitThread();
|
SslInitThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslContext::CipherList(const char *list)
|
bool SslContext::CipherList(const char *list)
|
||||||
{
|
{
|
||||||
ASSERT(ssl_ctx);
|
ASSERT(ssl_ctx);
|
||||||
return SSL_CTX_set_cipher_list(ssl_ctx, list);
|
return SSL_CTX_set_cipher_list(ssl_ctx, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SslContext::UseCertificate(String certdata, String pkeydata, bool cert_asn1)
|
bool SslContext::UseCertificate(String certdata, String pkeydata, bool cert_asn1)
|
||||||
{
|
{
|
||||||
ASSERT(ssl_ctx);
|
ASSERT(ssl_ctx);
|
||||||
if(IsNull(certdata) || IsNull(pkeydata))
|
if(IsNull(certdata) || IsNull(pkeydata))
|
||||||
return false;
|
return false;
|
||||||
SslCertificate cert;
|
SslCertificate cert;
|
||||||
SslKey pkey;
|
SslKey pkey;
|
||||||
if(!cert.Load(certdata, cert_asn1) || !pkey.Load(pkeydata))
|
if(!cert.Load(certdata, cert_asn1) || !pkey.Load(pkeydata))
|
||||||
return false;
|
return false;
|
||||||
if(!SSL_CTX_use_certificate(ssl_ctx, cert) || !SSL_CTX_use_PrivateKey(ssl_ctx, pkey))
|
if(!SSL_CTX_use_certificate(ssl_ctx, cert) || !SSL_CTX_use_PrivateKey(ssl_ctx, pkey))
|
||||||
return false;
|
return false;
|
||||||
if(!SSL_CTX_check_private_key(ssl_ctx))
|
if(!SSL_CTX_check_private_key(ssl_ctx))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SslContext::VerifyPeer(bool verify, int depth)
|
void SslContext::VerifyPeer(bool verify, int depth)
|
||||||
{
|
{
|
||||||
ASSERT(ssl_ctx);
|
ASSERT(ssl_ctx);
|
||||||
SSL_CTX_set_verify(ssl_ctx, verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
SSL_CTX_set_verify(ssl_ctx, verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
|
||||||
SSL_CTX_set_verify_depth(ssl_ctx, depth);
|
SSL_CTX_set_verify_depth(ssl_ctx, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslGetLastError(int& code)
|
String SslGetLastError(int& code)
|
||||||
{
|
{
|
||||||
char errbuf[150];
|
char errbuf[150];
|
||||||
ERR_error_string(code = ERR_get_error(), errbuf);
|
ERR_error_string(code = ERR_get_error(), errbuf);
|
||||||
return errbuf;
|
return errbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslGetLastError()
|
String SslGetLastError()
|
||||||
{
|
{
|
||||||
int dummy;
|
int dummy;
|
||||||
return SslGetLastError(dummy);
|
return SslGetLastError(dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
String SslToString(X509_NAME *name)
|
String SslToString(X509_NAME *name)
|
||||||
{
|
{
|
||||||
char buffer[500];
|
char buffer[500];
|
||||||
return X509_NAME_oneline(name, buffer, sizeof(buffer));
|
return X509_NAME_oneline(name, buffer, sizeof(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
Date ASN1ToDate(ASN1_STRING *time)
|
Date Asn1ToDate(ASN1_STRING *time)
|
||||||
{
|
{
|
||||||
if(!time) return Null;
|
if(!time) return Null;
|
||||||
int digit = 0;
|
int digit = 0;
|
||||||
while(digit < time->length && IsDigit(time->data[digit]))
|
while(digit < time->length && IsDigit(time->data[digit]))
|
||||||
digit++;
|
digit++;
|
||||||
if(digit < 6)
|
if(digit < 6)
|
||||||
return Null;
|
return Null;
|
||||||
int year2 = time->data[0] * 10 + time->data[1] - 11 * '0';
|
int year2 = time->data[0] * 10 + time->data[1] - 11 * '0';
|
||||||
int month = time->data[2] * 10 + time->data[3] - 11 * '0';
|
int month = time->data[2] * 10 + time->data[3] - 11 * '0';
|
||||||
int day = time->data[4] * 10 + time->data[5] - 11 * '0';
|
int day = time->data[4] * 10 + time->data[5] - 11 * '0';
|
||||||
return Date(year2 + (year2 < 90 ? 2000 : 1900), month, day);
|
return Date(year2 + (year2 < 90 ? 2000 : 1900), month, day);
|
||||||
}
|
}
|
||||||
|
|
||||||
String ASN1ToString(ASN1_STRING *s)
|
String Asn1ToString(ASN1_STRING *s)
|
||||||
{
|
{
|
||||||
return String(s->data, s->length);
|
return String(s->data, s->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
END_UPP_NAMESPACE
|
END_UPP_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,7 @@ TcpSocket::TcpSocket()
|
||||||
Reset();
|
Reset();
|
||||||
timeout = Null;
|
timeout = Null;
|
||||||
waitstep = 20;
|
waitstep = 20;
|
||||||
|
asn1 = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TcpSocket::Open(int family, int type, int protocol)
|
bool TcpSocket::Open(int family, int type, int protocol)
|
||||||
|
|
@ -343,9 +344,14 @@ bool TcpSocket::Accept(TcpSocket& ls)
|
||||||
Close();
|
Close();
|
||||||
Init();
|
Init();
|
||||||
Reset();
|
Reset();
|
||||||
|
ASSERT(ls.IsOpen());
|
||||||
if(timeout && !ls.WaitRead())
|
if(timeout) {
|
||||||
return false;
|
int h = ls.GetTimeout();
|
||||||
|
bool b = ls.Timeout(timeout).Wait(WAIT_READ, GetEndTime());
|
||||||
|
ls.Timeout(h);
|
||||||
|
if(!b)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if(!Open(ls.ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0))
|
if(!Open(ls.ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0))
|
||||||
return false;
|
return false;
|
||||||
socket = accept(ls.GetSOCKET(), NULL, NULL);
|
socket = accept(ls.GetSOCKET(), NULL, NULL);
|
||||||
|
|
@ -519,12 +525,11 @@ String TcpSocket::GetHostName()
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TcpSocket::RawWait(dword flags)
|
bool TcpSocket::RawWait(dword flags, int end_time)
|
||||||
{
|
{
|
||||||
LLOG("Wait(" << timeout << ", " << flags << ")");
|
LLOG("Wait(" << msecs() << " - " << end_time << ", " << flags << ")");
|
||||||
if((flags & WAIT_READ) && ptr != end)
|
if((flags & WAIT_READ) && ptr != end)
|
||||||
return true;
|
return true;
|
||||||
int end_time = msecs() + timeout;
|
|
||||||
if(socket == INVALID_SOCKET)
|
if(socket == INVALID_SOCKET)
|
||||||
return false;
|
return false;
|
||||||
for(;;) {
|
for(;;) {
|
||||||
|
|
@ -564,9 +569,19 @@ bool TcpSocket::RawWait(dword flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TcpSocket::Wait(dword flags, int end_time)
|
||||||
|
{
|
||||||
|
return ssl ? ssl->Wait(flags, end_time) : RawWait(flags, end_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TcpSocket::GetEndTime() const
|
||||||
|
{
|
||||||
|
return IsNull(timeout) ? INT_MAX : msecs() + timeout;
|
||||||
|
}
|
||||||
|
|
||||||
bool TcpSocket::Wait(dword flags)
|
bool TcpSocket::Wait(dword flags)
|
||||||
{
|
{
|
||||||
return ssl ? ssl->Wait(flags) : RawWait(flags);
|
return Wait(flags, GetEndTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
int TcpSocket::Put(const char *s, int length)
|
int TcpSocket::Put(const char *s, int length)
|
||||||
|
|
@ -579,8 +594,9 @@ int TcpSocket::Put(const char *s, int length)
|
||||||
return 0;
|
return 0;
|
||||||
done = 0;
|
done = 0;
|
||||||
bool peek = false;
|
bool peek = false;
|
||||||
|
int end_time = GetEndTime();
|
||||||
while(done < length) {
|
while(done < length) {
|
||||||
if(peek && !WaitWrite())
|
if(peek && !Wait(WAIT_WRITE, end_time))
|
||||||
return done;
|
return done;
|
||||||
peek = false;
|
peek = false;
|
||||||
int count = Send(s + done, length - done);
|
int count = Send(s + done, length - done);
|
||||||
|
|
@ -595,6 +611,26 @@ int TcpSocket::Put(const char *s, int length)
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TcpSocket::PutAll(const char *s, int len)
|
||||||
|
{
|
||||||
|
if(Put(s, len) != len) {
|
||||||
|
if(!IsError())
|
||||||
|
SetSockError("GePutAll", -1, "timeout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TcpSocket::PutAll(const String& s)
|
||||||
|
{
|
||||||
|
if(Put(s) != s.GetCount()) {
|
||||||
|
if(!IsError())
|
||||||
|
SetSockError("GePutAll", -1, "timeout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int TcpSocket::RawRecv(void *buf, int amount)
|
int TcpSocket::RawRecv(void *buf, int amount)
|
||||||
{
|
{
|
||||||
int res = recv(socket, (char *)buf, amount, 0);
|
int res = recv(socket, (char *)buf, amount, 0);
|
||||||
|
|
@ -619,10 +655,10 @@ int TcpSocket::Recv(void *buffer, int maxlen)
|
||||||
return ssl ? ssl->Recv(buffer, maxlen) : RawRecv(buffer, maxlen);
|
return ssl ? ssl->Recv(buffer, maxlen) : RawRecv(buffer, maxlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TcpSocket::ReadBuffer()
|
void TcpSocket::ReadBuffer(int end_time)
|
||||||
{
|
{
|
||||||
ptr = end = buffer;
|
ptr = end = buffer;
|
||||||
if(WaitRead())
|
if(Wait(WAIT_READ, end_time))
|
||||||
end = buffer + Recv(buffer, BUFFERSIZE);
|
end = buffer + Recv(buffer, BUFFERSIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -630,16 +666,21 @@ int TcpSocket::Get_()
|
||||||
{
|
{
|
||||||
if(!IsOpen() || IsError() || IsEof() || IsAbort())
|
if(!IsOpen() || IsError() || IsEof() || IsAbort())
|
||||||
return -1;
|
return -1;
|
||||||
ReadBuffer();
|
ReadBuffer(GetEndTime());
|
||||||
return ptr < end ? *ptr++ : -1;
|
return ptr < end ? *ptr++ : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int TcpSocket::Peek_(int end_time)
|
||||||
|
{
|
||||||
|
if(!IsOpen() || IsError() || IsEof() || IsAbort())
|
||||||
|
return -1;
|
||||||
|
ReadBuffer(end_time);
|
||||||
|
return ptr < end ? *ptr : -1;
|
||||||
|
}
|
||||||
|
|
||||||
int TcpSocket::Peek_()
|
int TcpSocket::Peek_()
|
||||||
{
|
{
|
||||||
if(!IsOpen() || IsError() || IsEof() || IsAbort())
|
return Peek_(GetEndTime());
|
||||||
return -1;
|
|
||||||
ReadBuffer();
|
|
||||||
return ptr < end ? *ptr : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int TcpSocket::Get(void *buffer, int count)
|
int TcpSocket::Get(void *buffer, int count)
|
||||||
|
|
@ -663,8 +704,9 @@ int TcpSocket::Get(void *buffer, int count)
|
||||||
ptr += count;
|
ptr += count;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
int end_time = GetEndTime();
|
||||||
while(done < count && !IsError() && !IsEof()) {
|
while(done < count && !IsError() && !IsEof()) {
|
||||||
if(!WaitRead())
|
if(!Wait(WAIT_READ, end_time))
|
||||||
break;
|
break;
|
||||||
int part = Recv((char *)buffer + done, count - done);
|
int part = Recv((char *)buffer + done, count - done);
|
||||||
if(part > 0)
|
if(part > 0)
|
||||||
|
|
@ -687,19 +729,41 @@ String TcpSocket::Get(int count)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TcpSocket::GetAll(void *buffer, int len)
|
||||||
|
{
|
||||||
|
if(Get(buffer, len) == len)
|
||||||
|
return true;
|
||||||
|
if(!IsError())
|
||||||
|
SetSockError("GetAll", -1, "timeout");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
String TcpSocket::GetAll(int len)
|
String TcpSocket::GetAll(int len)
|
||||||
{
|
{
|
||||||
String s = Get(len);
|
String s = Get(len);
|
||||||
return s.GetCount() == len ? s : String::GetVoid();
|
if(s.GetCount() != len) {
|
||||||
|
if(IsEof())
|
||||||
|
return s;
|
||||||
|
if(!IsError())
|
||||||
|
SetSockError("GetAll", -1, "timeout");
|
||||||
|
return String::GetVoid();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
String TcpSocket::GetLine(int maxlen)
|
String TcpSocket::GetLine(int maxlen)
|
||||||
{
|
{
|
||||||
String ln;
|
String ln;
|
||||||
|
int end_time = GetEndTime();
|
||||||
for(;;) {
|
for(;;) {
|
||||||
int c = Peek();
|
int c = Peek(end_time);
|
||||||
if(c < 0)
|
if(c < 0) {
|
||||||
|
if(IsEof())
|
||||||
|
return ln;
|
||||||
|
if(!IsError())
|
||||||
|
SetSockError("GetLine", -1, "timeout");
|
||||||
return String::GetVoid();
|
return String::GetVoid();
|
||||||
|
}
|
||||||
Get();
|
Get();
|
||||||
if(c == '\n')
|
if(c == '\n')
|
||||||
return ln;
|
return ln;
|
||||||
|
|
@ -767,6 +831,13 @@ bool TcpSocket::SSLHandshake()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TcpSocket::SSLCertificate(const String& cert_, const String& pkey_, bool asn1_)
|
||||||
|
{
|
||||||
|
cert = cert_;
|
||||||
|
pkey = pkey_;
|
||||||
|
asn1 = asn1_;
|
||||||
|
}
|
||||||
|
|
||||||
int SocketWaitEvent::Wait(int timeout)
|
int SocketWaitEvent::Wait(int timeout)
|
||||||
{
|
{
|
||||||
FD_ZERO(read);
|
FD_ZERO(read);
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ dword GetTickCount() {
|
||||||
gettimeofday(tv, tz);
|
gettimeofday(tv, tz);
|
||||||
return (dword)tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
return (dword)tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int msecs(int from) { return int((GetTickCount() - (dword)from) & 0x7fffffff); }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void TimeStop::Reset()
|
void TimeStop::Reset()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ static const int _MAX_PATH = PATH_MAX;
|
||||||
dword GetTickCount();
|
dword GetTickCount();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int msecs(int from = 0);
|
||||||
|
|
||||||
class TimeStop : Moveable<TimeStop> {
|
class TimeStop : Moveable<TimeStop> {
|
||||||
dword starttime;
|
dword starttime;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,6 @@
|
||||||
|
|
||||||
NAMESPACE_UPP
|
NAMESPACE_UPP
|
||||||
|
|
||||||
String WwwFormat(Time tm)
|
|
||||||
{
|
|
||||||
static const char *dayofweek[] =
|
|
||||||
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
|
||||||
static const char *month[] =
|
|
||||||
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
|
||||||
return String().Cat()
|
|
||||||
<< dayofweek[DayOfWeek(tm)] << ", "
|
|
||||||
<< (int)tm.day << ' ' << month[tm.month - 1]
|
|
||||||
<< ' ' << (int)tm.year
|
|
||||||
<< ' ' << Sprintf("%2d:%02d:%02d +0100", tm.hour, tm.minute, tm.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSameTextFile(const char *p, const char *q)
|
bool IsSameTextFile(const char *p, const char *q)
|
||||||
{
|
{
|
||||||
for(;;)
|
for(;;)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#ifndef __tweb_util__
|
#ifndef __tweb_util__
|
||||||
#define __tweb_util__
|
#define __tweb_util__
|
||||||
|
|
||||||
String WwwFormat(Time tm);
|
|
||||||
bool IsSameTextFile(const char *p, const char *q);
|
bool IsSameTextFile(const char *p, const char *q);
|
||||||
String StringSample(const char *s, int limit);
|
String StringSample(const char *s, int limit);
|
||||||
String GetRandomIdent(int length);
|
String GetRandomIdent(int length);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue