#include "Core.h" NAMESPACE_UPP static const char *s_www_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; String WwwFormat(Time tm) { static const char *dayofweek[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; return String().Cat() << dayofweek[DayOfWeek(tm)] << ", " << (int)tm.day << ' ' << s_www_month[tm.month - 1] << ' ' << (int)tm.year << ' ' << Sprintf("%2d:%02d:%02d " + GetTimeZoneText(), tm.hour, tm.minute, tm.second); } int FindMonth(const String& m) { for(int i = 0; i < 12; i++) if(s_www_month[i] == m) return i; return -1; } bool ScanWwwTime(const char *s, Time& tm) { CParser p(s); try { if(p.IsId()) { // Skip day of week p.SkipTerm(); p.Char(','); } tm.day = p.ReadInt(1, 31); int n = FindMonth(p.ReadId()) + 1; if(n < 1 || n > 12) return false; tm.month = n; tm.year = p.ReadInt(1, 4000); if(tm.year < 50) tm.year += 2000; else if(tm.year < 100) tm.year += 1900; tm.hour = p.ReadInt(0, 23); p.PassChar(':'); tm.minute = p.ReadInt(0, 59); if(p.Char(':')) tm.second = p.ReadInt(0, 59); tm += 60 * (GetTimeZone() - ScanTimeZone(p.GetPtr())); } catch(CParser::Error) { return false; } return tm.IsValid(); } Time ScanWwwTime(const char *s) { Time tm; if(ScanWwwTime(s, tm)) return tm; return Null; } String MIMECharsetName(byte charset) { if(charset == CHARSET_DEFAULT) charset = GetDefaultCharset(); switch(charset) { case CHARSET_ISO8859_1: return "ISO-8859-1"; case CHARSET_ISO8859_2: return "ISO-8859-2"; case CHARSET_ISO8859_3: return "ISO-8859-3"; case CHARSET_ISO8859_4: return "ISO-8859-4"; case CHARSET_ISO8859_5: return "ISO-8859-5"; case CHARSET_ISO8859_6: return "ISO-8859-6"; case CHARSET_ISO8859_7: return "ISO-8859-7"; case CHARSET_ISO8859_8: return "ISO-8859-8"; case CHARSET_ISO8859_9: return "ISO-8859-9"; case CHARSET_ISO8859_10: return "ISO-8859-10"; case CHARSET_ISO8859_13: return "ISO-8859-13"; case CHARSET_ISO8859_14: return "ISO-8859-14"; case CHARSET_ISO8859_15: return "ISO-8859-15"; case CHARSET_ISO8859_16: return "ISO-8859-16"; case CHARSET_WIN1250: return "windows-1250"; case CHARSET_WIN1251: return "windows-1251"; case CHARSET_WIN1252: return "windows-1252"; case CHARSET_WIN1253: return "windows-1253"; case CHARSET_WIN1254: return "windows-1254"; case CHARSET_WIN1255: return "windows-1255"; case CHARSET_WIN1256: return "windows-1256"; case CHARSET_WIN1257: return "windows-1257"; case CHARSET_WIN1258: return "windows-1258"; // case CHARSET_KOI8_R: // case CHARSET_CP852: // case CHARSET_MJK: case CHARSET_TOASCII: return "us-ascii"; case CHARSET_UTF8: return "UTF-8"; // case CHARSET_UNICODE: default: return Null; } } static const char hex_digits[] = "0123456789ABCDEF"; String UrlEncode(const char *p, const char *e) { StringBuffer out; out.Reserve((int)(e - p)); for(; p < e; p++) { const char *b = p; while(p < e && (byte)*p > ' ' && (byte)*p < 127 && (IsAlNum(*p) || *p == '.' || *p == '-' || *p == '_')) p++; if(p > b) out.Cat(b, int(p - b)); if(p >= e) break; if(*p == ' ') out << '+'; else out << '%' << hex_digits[(*p >> 4) & 15] << hex_digits[*p & 15]; } return out; } String UrlEncode(const char *s, int len) { return UrlEncode(s, s + len); } String UrlEncode(const String& s) { return UrlEncode(~s, s.GetLength()); } String UrlDecode(const char *b, const char *e) { StringBuffer out; byte d1, d2, d3, d4; for(const char *p = b; p < e; p++) if(*p == '+') out.Cat(' '); else if(*p == '%' && (d1 = ctoi(p[1])) < 16 && (d2 = ctoi(p[2])) < 16) { out.Cat(d1 * 16 + d2); p += 2; } else if(*p == '%' && (p[1] == 'u' || p[1] == 'U') && (d1 = ctoi(p[2])) < 16 && (d2 = ctoi(p[3])) < 16 && (d3 = ctoi(p[4])) < 16 && (d4 = ctoi(p[5])) < 16) { out.Cat(WString((d1 << 12) | (d2 << 8) | (d3 << 4) | d4, 1).ToString()); p += 5; } else out.Cat(*p); return out; } String UrlDecode(const char *s, int len) { return UrlDecode(s, s + len); } String UrlDecode(const String& s) { return UrlDecode(~s, s.GetLength()); } String QPEncode(const char* s) { StringBuffer r; int len = 0; const int limit = 70; while(*s) { if(s[0] >= 33 && s[0] <= 126 && s[0] != '=' && s[0] != '_') { r.Cat(s[0]); len++; } else if(s[0] == '\r') ; else if(s[0] == '\n') { r.Cat("\r\n"); len = 0; } else // Encode HT or SP only if they are at the end of line (before CRLF or EOF) if((s[0] == ' ' || s[0] == '\t') && (s[1] && s[1] != '\n') && (s[1] != '\r' || (s[1] == '\r' && s[2] && s[2] != '\n'))) { r.Cat(s[0]); len++; } else { static const char hex[] = "0123456789ABCDEF"; r.Cat('='); r.Cat(hex[(s[0] >> 4) & 15]); r.Cat(hex[s[0] & 15]); len += 3; } if(len > limit) { // Soft-break. r.Cat('='); r.Cat("\r\n"); len = 0; } s++; } return r; } String QPDecode(const char *s, bool underscore_to_space) { StringBuffer r; while(*s) { int c = *s++; if(c == '=') { int c1 = '0', c2 = '0'; if(*s) c1 = *s++; if(*s) c2 = *s++; if(IsXDigit(c1) && IsXDigit(c2)) r.Cat((ctoi(c1) << 4) | ctoi(c2)); } else if(underscore_to_space && c == '_') r.Cat(' '); else r.Cat(c); } return r; } String Base64Encode(const char *b, const char *e) { static const char encoder[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; if(b == e) return Null; int out = (int(e - b) + 2) / 3 * 4; int rem = int(e - b) % 3; e -= rem; StringBuffer s(out); char *p = s; while(b < e) { p[0] = encoder[(b[0] >> 2) & 0x3F]; p[1] = encoder[((b[0] << 4) & 0x30) | ((b[1] >> 4) & 0x0F)]; p[2] = encoder[((b[1] << 2) & 0x3C) | ((b[2] >> 6) & 0x03)]; p[3] = encoder[b[2] & 0x3F]; b += 3; p += 4; } if(rem == 1) { p[0] = encoder[(b[0] >> 2) & 0x3F]; p[1] = encoder[(b[0] << 4) & 0x30]; p[2] = p[3] = '='; } else if(rem == 2) { p[0] = encoder[(b[0] >> 2) & 0x3F]; p[1] = encoder[((b[0] << 4) & 0x30) | ((b[1] >> 4) & 0x0F)]; p[2] = encoder[(b[1] << 2) & 0x3C]; p[3] = '='; } return s; } String Base64Encode(const char *b, int len) { return Base64Encode(b, b + len); } String Base64Encode(const String& data) { return Base64Encode(~data, data.GetCount()); } String Base64Decode(const char *b, const char *e) { static byte dec64[] = { /* 0x */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 1x */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 2x */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F, /* 3x */0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 4x */0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 5x */0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 6x */0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, /* 7x */0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 8x */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 9x */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Ax */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Bx */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Cx */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Dx */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Ex */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Fx */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; StringBuffer out; byte c[4]; int pos = 0; for(; b < e; b++) if((byte)*b > ' ') { byte ch = dec64[(byte)*b]; if(ch & 0xC0) break; c[pos++] = ch; if(pos == 4) { out.Cat((c[0] << 2) | (c[1] >> 4)); out.Cat((c[1] << 4) | (c[2] >> 2)); out.Cat((c[2] << 6) | (c[3] >> 0)); pos = 0; } } if(pos >= 2) { out.Cat((c[0] << 2) | (c[1] >> 4)); if(pos >= 3) { out.Cat((c[1] << 4) | (c[2] >> 2)); if(pos >= 4) out.Cat((c[2] << 6) | (c[3] >> 0)); } } return out; } String Base64Decode(const char *s, int len) { return Base64Decode(s, s + len); } String Base64Decode(const String& data) { return Base64Decode(~data, data.GetLength()); } String DeHtml(const char *s) { String result; while(*s) { if(*s == 31) result.Cat(" "); else if(*s == '<') result.Cat("<"); else if(*s == '>') result.Cat(">"); else if(*s == '&') result.Cat("&"); else if(*s == '\"') result.Cat("""); else if((byte)*s < ' ') result.Cat(NFormat("&#%d;", (byte)*s)); else result.Cat(*s); s++; } return result; } void HMAC_SHA1(const byte *text, int text_len, const byte *key, int key_len, byte *digest) { unsigned char k_ipad[65]; unsigned char k_opad[65]; unsigned char tk[20]; int i; if(key_len > 64) { SHA1(tk, key, key_len); key = tk; key_len = 20; } memset( k_ipad, 0, sizeof(k_ipad)); memset( k_opad, 0, sizeof(k_opad)); memcpy( k_ipad, key, key_len); memcpy( k_opad, key, key_len); for(i = 0; i < 64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } Sha1Stream sha1; sha1.Put(k_ipad, 64); sha1.Put(text, text_len); sha1.Finish(digest); sha1.New(); sha1.Put(k_opad, 64); sha1.Put(digest, 20); sha1.Finish(digest); } String HMAC_SHA1(const String& text, const String& key) { byte digest[20]; HMAC_SHA1(text, text.GetCount(), key, key.GetCount(), digest); return String(digest, 20); } String HMAC_SHA1_Hex(const String& text, const String& key) { return HexString(HMAC_SHA1(text, key)); } void HttpCookie::Clear() { id.Clear(); value.Clear(); domain.Clear(); path.Clear(); raw.Clear(); } bool HttpCookie::Parse(const String& cookie) { Clear(); const char *s = cookie; raw = cookie; bool first = true; while(s && *s) { while(*s == ' ') s++; const char *e = strchr(s, ';'); if(!e) e = s + strlen(s); const char *eq = strchr(s, '='); if(eq && eq < e) { String h = String(s, eq); if(first) { id = h; value = String(eq + 1, e); first = false; } else { h = ToLower(h); if(h == "domain") domain = String(eq + 1, e); if(h == "path") path = String(eq + 1, e); } } if(*e == 0) break; s = e + 1; } return !first; } void HttpHeader::Clear() { first_line.Clear(); fields.Clear(); cookies.Clear(); f1 = f2 = f3 = Null; scgi = false; } int64 HttpHeader::GetContentLength() const { return Nvl(ScanInt64((*this)["content-length"]), (int64)0); } void HttpHeader::Add(const String& id_, const String& value) { String id = ToLower(id_); fields.Add(id, value); if(id == "set-cookie") { HttpCookie c; if(c.Parse(value)) cookies.Add(c.id, c); } } bool HttpHeader::ParseAdd(const String& hdrs) { StringStream ss(hdrs); first_line = ss.GetLine(); while(!ss.IsEof()) { String s = ss.GetLine(); if(s.IsEmpty()) break; int q = s.Find(':'); if(q >= 0) Add(ToLower(s.Mid(0, q)), TrimLeft(s.Mid(q + 1))); } const char *s = first_line; if((byte)*s <= ' ') return false; while(*s != ' ' && *s) f1.Cat(*s++); while(*s == ' ') s++; if(!*s) return false; while(*s != ' ' && *s) f2.Cat(*s++); while(*s == ' ') s++; f3 = s; return true; } bool HttpHeader::Parse(const String& hdrs) { Clear(); return ParseAdd(hdrs); } int CharFilterScgiHttp(int c) { return c == '_' ? '-' : c; } bool HttpHeader::ParseSCGI(const String& scgi_hdr) { Clear(); scgi = true; String key, uri, qs; const char *b = scgi_hdr; const char *e = scgi_hdr.End(); int64 content_length = Null; for(const char *s = scgi_hdr; s < e; s++) { if(*s == '\0') { String h(b, s); b = s + 1; if(key.GetCount()) { if(key.StartsWith("http_")) Add(Filter(key.Mid(5), CharFilterScgiHttp), h); if(key == "content_length") content_length = ScanInt64(h); if(key == "request_method") f1 = h; if(key == "request_uri") uri = h; if(key == "query_string") qs = h; if(key == "server_protocol") f3 = h; key.Clear(); } else key = ToLower(h); } } f2 = uri + qs; first_line = f1 + ' ' + f2 + ' ' + f3; if(!IsNull(content_length) && content_length && fields.Find("content-length") < 0) fields.Add("content-length", AsString(content_length)); return true; } bool HttpHeader::Read(TcpSocket& socket) { Clear(); String h; if(IsDigit(socket.Peek())) { int len = 0; while(IsDigit(socket.Peek())) len = 10 * len + socket.Get() - '0'; if(socket.Get() != ':' || len < 0 || len > 10000000) return false; h = socket.GetAll(len); if(socket.Get() != ',') return false; return ParseSCGI(h); } h = socket.GetLine(); if(h.IsVoid()) return false; h << "\r\n"; for(;;) { String s = socket.GetLine(); if(s.IsVoid()) return false; if(s.IsEmpty()) break; h << s << "\r\n"; } return Parse(h); } int HttpHeader::GetCode() const { return ScanInt(f2); } bool HttpHeader::Request(String& method, String& uri, String& version) const { method = GetMethod(); uri = GetURI(); version = GetVersion(); return true; } String HttpHeader::GetCookie(const char *id) const { int q = cookies.Find(id); return q < 0 ? String() : cookies[q].value; } bool HttpHeader::Response(String& protocol, int& code, String& reason) const { protocol = GetProtocol(); code = GetCode(); reason = GetReason(); return !IsNull(code); } bool HttpResponse(TcpSocket& socket, bool scgi, int code, const char *phrase, const char *content_type, const String& data, const char *server, bool gzip) { String r; r << (scgi ? "Status: " : "HTTP/1.1 ") << code << ' ' << phrase << "\r\n" "Date: " << WwwFormat(GetUtcTime()) << "\r\n" "Server: " << (server ? server : "U++ based server") << "\r\n" "Connection: close\r\n"; if(data.GetCount()) r << "Content-Length: " << data.GetCount() << "\r\n"; if(content_type) r << "Content-Type: " << content_type << "\r\n"; if(gzip) r << "Content-Encoding: gzip\r\n"; r << "\r\n"; if(!socket.PutAll(r)) return false; return data.GetCount() == 0 || socket.PutAll(data); } String UrlInfo::operator[](const char *id) const { return parameters.Get(id, String()); } const Vector& UrlInfo::GetArray(const char *id) const { static Vector empty; return array_parameters.Get(id, empty); } void UrlInfo::Parse(const String& url_) { Clear(); url = url_; String h = url; int q = h.ReverseFind('#'); if(q >= 0) { fragment = UrlDecode(h.Mid(q + 1)); h.Trim(q); } q = h.ReverseFind('?'); if(q >= 0) { query = UrlDecode(h.Mid(q + 1)); h.Trim(q); } q = h.Find("://"); if(q >= 0) { scheme = UrlDecode(h.Mid(0, q)); h = h.Mid(q + 3); } else { int q = 0; while(h[q] == '/') q++; h.Remove(0, q); } q = h.Find('@'); if(q >= 0) { username = h.Mid(0, q); h = h.Mid(q + 1); q = username.Find(':'); if(q >= 0) { password = username.Mid(q + 1); username.Trim(q); } } q = h.Find('/'); if(q >= 0) { path = UrlDecode(h.Mid(q)); h.Trim(q); } q = h.Find(':'); if(q >= 0) { port = UrlDecode(h.Mid(q + 1)); h.Trim(q); } host = h; const char *p = query; while(*p) { const char *last = p; while(*p && *p != '=' && *p != '&') p++; String key = UrlDecode(last, p); if(*p == '=') p++; last = p; while(*p && *p != '&') p++; if(*key != '.' && *key != '@') { String val = UrlDecode(last, p); if(key.EndsWith("[]")) array_parameters.GetAdd(key.Mid(0, key.GetCount() - 2)) << val; else parameters.GetAdd(key) = UrlDecode(last, p); } if(*p) p++; } } END_UPP_NAMESPACE