#include <base/Application.h>
#include <base/Timer.h>
#include <base/Trace.h>
#include <base/Primitives.h>
#include <base/UnsignedInteger.h>
#include <base/concurrency/Thread.h>
#include <base/io/File.h>
#include <base/net/StreamSocket.h>
#include <base/net/InetEndPoint.h>
#include <base/net/InetInterface.h>
#include <base/net/InetService.h>
#include <base/net/ServerSocket.h>
#include <base/net/Url.h>
#include <base/string/FormatInputStream.h>
#include <base/string/FormatOutputStream.h>
#include <base/string/StringOutputStream.h>
using namespace com::azure::dev::base;
namespace commands {
const Literal METHOD_OPTIONS = MESSAGE(
"OPTIONS");
const Literal METHOD_GET = MESSAGE(
"GET");
const Literal METHOD_HEAD = MESSAGE(
"HEAD");
const Literal METHOD_POST = MESSAGE(
"POST");
const Literal METHOD_PUT = MESSAGE(
"PUT");
const Literal METHOD_DELETE = MESSAGE(
"DELETE");
const Literal METHOD_TRACE = MESSAGE(
"TRACE");
const Literal METHOD_CONNECT = MESSAGE(
"CONNECT");
const Literal CMD_ACCOUNT = MESSAGE(
"ACCT");
const Literal CMD_CDUP = MESSAGE(
"CDUP");
const Literal CMD_CWD = MESSAGE(
"CWD");
const Literal CMD_LOGOUT = MESSAGE(
"QUIT");
const Literal CMD_PASSWORD = MESSAGE(
"PASS");
const Literal CMD_REINITIALIZE = MESSAGE(
"REIN");
const Literal CMD_USER = MESSAGE(
"USER");
const Literal CMD_DATA_PORT = MESSAGE(
"PORT");
const Literal CMD_PASSIVE = MESSAGE(
"PASV");
const Literal CMD_REPRESENTATION = MESSAGE(
"TYPE");
const Literal CMD_FILE_STRUCTURE = MESSAGE(
"STRU");
const Literal CMD_TRANSFER_MODE = MESSAGE(
"MODE");
};
using namespace commands;
private:
bool permanent = true;
public:
{
}
{
}
inline bool isPermanent() const noexcept
{
return permanent;
}
};
class HTTPTraits {
public:
static const char SP = ' ';
static inline bool isLWS(char value) {
return (value == ' ') || (value == '\t');
}
static inline bool isText(char value) {
}
static inline bool isSeparator(char value) {
switch (value) {
case '\t': case ' ':
case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':':
case '\\': case '"': case '/': case '[': case ']': case '?': case '=': case '{': case '}':
return true;
}
return false;
}
static inline bool isToken(char value) {
}
static const char* SHORT_WEEKDAY[7];
static const char* LONG_WEEKDAY[7];
static const char* SHORT_MONTH[12];
};
const char* HTTPTraits::SHORT_WEEKDAY[7] = {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
const char* HTTPTraits::LONG_WEEKDAY[7] = {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
const char* HTTPTraits::SHORT_MONTH[12] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
class MessageHeader :
public Object {
private:
public:
MessageHeader(
const String& line) {
String::ReadIterator i = begin;
String::ReadIterator name = i;
for (; (i < end) && HTTPTraits::isToken(*i); ++i) {
}
this->name = line.
substring(name - begin, i - begin);
for (; (i < end) && HTTPTraits::isLWS(*i); ++i) {
}
String::ReadIterator value = i;
String::ReadIterator endValue = end;
while (value < endValue) {
--endValue;
if (!HTTPTraits::isLWS(*endValue)) {
++endValue;
break;
}
}
if (*i == '"') {
++i;
++value;
for (; (i < endValue) && (*i != '"') && HTTPTraits::isText(*i); ++i) {
}
bassert(
(i == --endValue) && (*i++ == '"'),
);
}
this->value = line.
substring(value - begin, endValue - begin);
}
return name;
}
return value;
}
};
public:
virtual bool pushBegin(long long totalSize) = 0;
virtual MemorySize push(const uint8* buffer, MemorySize size) = 0;
virtual void pushEnd() = 0;
};
public:
virtual long long pullBegin() const = 0;
virtual MemorySize pull(uint8* buffer, MemorySize size) = 0;
};
class PushToNothing {
public:
bool pushBegin(long long totalSize)
{
return true;
}
MemorySize push(const uint8* buffer, MemorySize size)
{
return size;
}
void pushEnd() {
}
};
public:
PushToStandardOutput() {
}
bool pushBegin(long long totalSize)
{
return true;
}
MemorySize push(const uint8* buffer, MemorySize size)
{
for (unsigned int i = 0; i < size;) {
char ch = *buffer++;
++i;
if (ch == '\n') {
fout << EOL;
if ((i < size) && (*buffer == '\r')) {
++buffer;
++i;
}
} else if (ch == '\r') {
fout << EOL;
if ((i < size) && (*buffer == '\n')) {
++buffer;
++i;
}
fout << ch;
} else if (ch != ' ') {
fout << '.';
} else {
fout << ' ';
}
}
return size;
}
void pushEnd() {
fout << ENDL;
}
virtual ~PushToStandardOutput() {
}
};
private:
long long bytesWritten = 0;
long long totalSize = 0;
public:
PushToFile(
File _file) : file(_file) {
}
bool pushBegin(long long totalSize) {
this->totalSize = totalSize;
return true;
}
MemorySize push(const uint8* buffer, MemorySize size)
{
unsigned int result = file.
write(buffer, size);
BASSERT(result == size);
bytesWritten += size;
if (totalSize > 0) {
fout << " bytes written=" << bytesWritten
<< " completed=" << base::FIXED << setWidth(7) << setPrecision(3)
<< static_cast<long double>(bytesWritten)/totalSize*100 << '%'
<< " rate=" << base::FIXED << setWidth(12) << setPrecision(3)
<< "kb/s\r" << FLUSH;
} else {
fout << " bytes written=" << bytesWritten
<< " rate=" << base::FIXED << setWidth(12) << setPrecision(3)
<< "kb/s\r" << FLUSH;
}
return size;
}
void pushEnd() {
fout << ENDL;
}
virtual ~PushToFile() {
}
};
class HypertextTransferProtocolClient :
public Object {
public:
static const unsigned int DEFAULT_RETRY_DELAY = 15;
static const unsigned int DEFAULT_RETRY_ATTEMPTS = 5;
public:
inline InvalidResponse()
{
}
inline InvalidResponse(const char* message)
{
}
};
typedef HTTPTraits Traits;
enum Verbosity {SILENT, SHORT, ALL, DEBUG_NORMAL, DEBUG_EXTENDED};
enum StatusClass {INFORMATION, SUCCESS, REDIRECTION, CLIENT_ERROR, SERVER_ERROR};
enum Method {OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT};
enum ContentType {TEXT, IMAGE, UNSPECIFIED};
struct Status {
StatusClass statusClass = CLIENT_ERROR;
int code = 0;
};
private:
bool responsePending = false;
Status status;
Verbosity verbosity = ALL;
unsigned int retryDelay = 0;
unsigned int retryAttempts = 0;
protected:
void translateStatus(
const String& value) {
bool valid = true;
bool validVersion = false;
bool validCode = false;
bool validPhrase = false;
if ((i < end) && (*i++ == 'H') &&
(i < end) && (*i++ == 'T') &&
(i < end) && (*i++ == 'T') &&
(i < end) && (*i++ == 'P') &&
(i < end) && (*i++ == '/') &&
++i;
}
++i;
}
validVersion = true;
}
}
if (!((i < end) && (*i++ == ' '))) {
valid = false;
}
if (valid && validVersion) {
if (end - i >= 3) {
char a = *i++;
char b = *i++;
char c = *i++;
static const StatusClass classes[] = {INFORMATION, SUCCESS, REDIRECTION, CLIENT_ERROR, SERVER_ERROR};
status.statusClass = classes[a - '1'];
status.code = (static_cast<int>(a-'0') * 10 + static_cast<int>(b-'0')) * 10 + static_cast<int>(c-'0');
validCode = true;
}
}
}
if (!((i < end) && (*i++ == ' '))) {
valid = false;
}
if (valid && validCode) {
while ((i < end) && (*i > 0x1f) && (*i < 0x7f)) {
reasonPhrase += *i++;
}
if (i == end) {
validPhrase = true;
}
}
bassert(valid && validVersion && validCode && validPhrase,
HTTPException(
"Invalid response."));
}
static const Literal AGENT = MESSAGE(
"http/0.1 (Base Framework)");
METHOD_OPTIONS,
METHOD_GET,
METHOD_HEAD,
METHOD_POST,
METHOD_PUT,
METHOD_DELETE,
METHOD_TRACE,
METHOD_CONNECT
};
stream << methods[method] << Traits::SP << resourceUri << Traits::SP
<< "HTTP/1.1" << CRLF
<< "Host: " << host << CRLF
<< "User-Agent: " << AGENT << CRLF
<< CRLF << FLUSH;
if (verbosity >= DEBUG_NORMAL) {
fout <<
"Request: " << stream.
getString() << ENDL;
}
}
{
controlConnection.
wait();
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: bytes available: " << instream.available() << ENDL;
}
BASSERT(instream.available() == controlConnection.
available());
instream >> statusLine;
if (verbosity >= DEBUG_NORMAL) {
fout << "Status-Line: " << statusLine << ENDL;
}
translateStatus(statusLine);
bool chunkedTransferEncoding = false;
bool hasContentLength = false;
unsigned int contentLength = 0;
while (true) {
instream >> line;
if (verbosity >= ALL) {
fout << ">> " << line << ENDL;
}
if (line.isEmpty()) {
break;
}
MessageHeader header(line);
fout << "name=" << header.getName() << Traits::SP
<< "value=" << header.getValue() << ENDL;
if (header.getName() == "Transfer-Encoding") {
if (header.getValue().toLowerCase() == "chunked") {
chunkedTransferEncoding = true;
}
} else if (header.getName() == "Content-Length") {
try {
hasContentLength = true;
}
} else if (header.getName() == "Content-Type") {
contentType = header.getValue();
}
}
if (chunkedTransferEncoding) {
unsigned long long totalLength = 0;
while (true) {
instream >> line;
const String::ReadIterator end = line.getEndReadIterator();
while ((i < end) && (*i == Traits::SP)) {
++i;
}
bassert(
InvalidResponse("Chunk size invalid.")
);
unsigned int chunkSize = 0;
++i;
}
if (chunkSize == 0) {
break;
}
totalLength += chunkSize;
BASSERT(push);
long long bytesRead = 0;
while (bytesRead < chunkSize) {
unsigned int bytesToRead = minimum<long long>(buffer.
getSize(), chunkSize - bytesRead);
unsigned int result = instream.read(buffer.
getElements(), bytesToRead);
bytesRead += result;
}
}
instream >> line;
BASSERT(line.isEmpty());
}
while (true) {
instream >> line;
if (verbosity >= ALL) {
fout << ">> " << line << ENDL;
}
if (line.isEmpty()) {
break;
}
}
} else if (hasContentLength) {
if (verbosity >= DEBUG_NORMAL) {
fout << "Reading content: " << contentLength << " byte(s)" << ENDL;
}
if (push) {
long long bytesRead = 0;
while (bytesRead < contentLength) {
unsigned int bytesToRead = minimum<long long>(buffer.
getSize(), contentLength - bytesRead);
unsigned int result = instream.read(buffer.
getElements(), bytesToRead);
bytesRead += result;
}
}
} else {
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: skipping " << contentLength << " byte(s)" << ENDL;
}
instream.skip(contentLength);
}
}
}
public:
static bool isValidString(
const String& str) {
return false;
}
return false;
}
}
return true;
}
static bool isValidPrintableString(
const String& str) {
return false;
}
if ((*i < 33) || (*i > 126)) {
return false;
}
}
return true;
}
HypertextTransferProtocolClient(
Verbosity _verbosity = DEBUG_EXTENDED)
: host(_host),
endPoint(_endPoint),
verbosity(_verbosity),
retryDelay(DEFAULT_RETRY_DELAY),
retryAttempts(DEFAULT_RETRY_ATTEMPTS),
buffer(4096 * 64) {
}
unsigned int getRetryDelay() const {
return retryDelay;
}
void setRetryDelay(unsigned int value) {
retryDelay = value;
}
unsigned int getRetryAttempts() const {
return retryAttempts;
}
void setRetryAttempts(unsigned int value) {
retryAttempts = value;
}
void connect() {
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Establishing control connection to: "
<<
"port=" << endPoint.
getPort() << ENDL;
}
}
void getOptions() {
String request = makeRequest(OPTIONS, host,
"*");
outstream << request << FLUSH;
PushToStandardOutput push;
getResponse(&push);
}
String request = makeRequest(GET, host, resource);
outstream << request << FLUSH;
} else {
String request = makeRequest(GET, host,
"/");
outstream << request << FLUSH;
}
getResponse(push);
}
~HypertextTransferProtocolClient() {
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Closing sockets..." << ENDL;
}
controlConnection.
close();
}
};
class HTTPClient :
public Object {
public:
HTTPClient(
const String& resource,
const String& filename) {
Url url(resource,
false);
if (url.getScheme().isEmpty()) {
url.setScheme("http");
}
fout << "Individual parts of the specified url:" << EOL
<< " scheme: " << url.getScheme() << EOL
<< " user: " << url.getUser() << EOL
<< " password: " << url.getPassword() << EOL
<< " host: " << url.getHost() << EOL
<< " port: "
<< (url.getPort().isProper() ? url.getPort() :
String(
"80")) << EOL
<< " path: " << url.getPath() << ENDL;
if (url.getScheme() != "http") {
fout << "Invalid url" << ENDL;
return;
}
{
fout << "Server addresses:" << ENDL;
unsigned int index = 0;
if (index == 0) {
address = temp;
fout << " address " << index++ << ": "
<< temp << " (USING THIS)" << ENDL;
} else {
fout << " address " << index++ << ": " << temp << ENDL;
}
}
}
address, (url.getPort().isProper() ? url.getPort() :
String(
"80"))
);
host = url.getHost() +
String(
":") + port;
} else {
host = url.getHost();
}
HypertextTransferProtocolClient client(host, endPoint);
client.connect();
client.getOptions();
client.getResource("/" + url.getPath(), &push);
} else {
PushToStandardOutput push;
client.getResource("/" + url.getPath(), &push);
}
}
};
private:
static const unsigned int MAJOR_VERSION = 1;
static const unsigned int MINOR_VERSION = 0;
public:
HTTPApplication()
{
}
void main()
{
fout << getFormalName() << " version "
<< MAJOR_VERSION << '.' << MINOR_VERSION << EOL
<< "The Base Framework (Test Suite)" << EOL
<< ENDL;
String url = MESSAGE(
"www.google.com/");
case 0:
break;
case 1:
url = arguments[0];
break;
case 2:
url = arguments[0];
file = arguments[1];
break;
default:
fout << "Usage: " << getFormalName() << " [url] [output]" << ENDL;
return;
}
HTTPClient client(url, file);
}
};
APPLICATION_STUB(HTTPApplication);