I'm trying to create a webserver to learn how HTTP functions. I am trying to send a png file to the browser, however the image successfully makes it.
Here is my png sending code:
std::ifstream in("P:/server"+location, std::ios::binary);
if(!in.is_open()) {
std::cout << "failed to open file" << std::endl;
in.close();
}
in.seekg(0, std::ios::end);
int length = in.tellg();
in.seekg(0, std::ios::beg);
char *data = new char[length];
in.read(data, length);
in.close();
std::string headers = "HTTP/1.1 200 OK\n\rContent-Length: " + std::to_string(length) + "\n\rConnection: keep-alive\n\rContent-Type: image/png\n\r\n\r";
int totalLength = headers.length() + length;
char *allData = new char[totalLength];
std::strcpy(allData, headers.data());
std::strcat(allData, data);
int bytes = send(socket, data, totalLength, NULL);
Once the server should have sent the image, it shows up as the missing image icon.
I have checked to make sure that all the bytes are being sent, and that the image is being loaded.
Any help would be very much appreciated!
There are quite a few mistakes in your code.
You are still trying to process the file if is_open() returns false.
You are using \n\r when you need to use \r\n instead.
You are using strcat() to append data to allData. When appending binary data, like a PNG, strcat() will truncate on the first 0x00 byte encountered. You need to use memcpy() (or equivalent) instead.
Your call to send() is sending data when it should be sending allData instead. So you are you not sending the HTTP headers at all, and you are sending data using the wrong length.
You are assuming send() will send all of the data you give it in a single operation. That is almost never the case, especially fo large amounts of data. send() returns the number of bytes it actually accepted for sending, so you need to call it in a loop until all of the data has been accepted.
You are not sending an error message to the client if something goes wrong while preparing the file.
That being said, allData is actually unnecessary, and is a waste of memory. TCP is a byte stream, so you can send headers and data individually one after the other. How many times you call send() doesn't affect how the other party receives the data.
You might also consider minimizing memory usage further by changing data to be a fixed-sized buffer so you can send() the content of in while reading from it in smaller chunks.
Try something more like this instead:
int sendData(int sckt, const void *data, int datalen)
{
const char *ptr = static_cast<const char*>(data);
while (datalen > 0) {
int bytes = send(sckt, ptr, datalen, 0);
if (bytes <=0) return -1;
ptr += bytes;
datalen -= bytes;
}
return 0;
}
int sendStr(int sckt, const std::string &s)
{
return sendData(sckt, s.c_str(), s.size());
}
...
std::string filename = "P:/server"+location;
if (!fileExists(filename)) // <- you need to implement this
{
if (sendStr(socket, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n") == -1) {
close(socket);
}
}
else
{
std::ifstream in(filename, std::ios::binary);
if (!in.is_open())
{
std::cout << "failed to open file" << std::endl;
if (sendStr(socket, "HTTP/1.1 500 Error\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n") == -1) {
close(socket);
}
}
else
{
in.seekg(0, std::ios::end);
std::size_t length = in.tellg();
in.seekg(0, std::ios::beg);
if (in.fail())
{
std::cout << "failed to get size of file" << std::endl;
if (sendStr(socket, "HTTP/1.1 500 Error\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n") == -1) {
close(socket);
}
}
else if (sendStr(socket, "HTTP/1.1 200 OK\r\nContent-Length: " + std::to_string(length) + "\r\nConnection: keep-alive\r\nContent-Type: image/png\r\n\r\n") == -1)
{
close(socket);
}
else if (length > 0)
{
char data[1024];
do
{
if (!in.read(data, std::min(length, sizeof(data))))
{
close (socket);
break;
}
int bytes = in.gcount();
if (sendData(socket, data, bytes) == -1)
{
close(socket);
break;
}
length -= bytes;
}
while (length > 0);
}
}
}
Related
I'm creating an FTP client.
I'm getting a gif from the server, but after that the gif is corrupted.
When I change the file extension to look at the diff, I see that the
CR/LF characters are gone.
How could this be? I made sure to use image mode.
Here's my read code in TCP socket.
string TCPSocket::long_read()
{
pollfd ufds;
ufds.fd = sd;
ufds.events = POLLIN;
ufds.revents = 0;
ssize_t bytesRead = 0;
string result;
char* buf = new char[LONGBUFLEN];
do {
bzero(buf, LONGBUFLEN);
bytesRead = ::read(sd, buf, LONGBUFLEN);
if (bytesRead == 0) {
break;
}
if (bytesRead > 0) {
result = result + string(buf, bytesRead);
}
} while (poll(&ufds, 1, 1000) > 0);
return result;
}
Here my get code in main.cpp
else if (command == command::GET) {
string filename;
cin >> filename;
string dataHost;
int dataPort;
if (enterPassiveMode(dataHost, dataPort)) {
dataSocket = new TCPSocket(dataHost.c_str(), dataPort);
if (fork() == 0) {
string result = dataSocket->long_read();
size_t length = result.size();
char* resultArr = new char[length];
memcpy(resultArr, result.data(), length);
// mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
FILE* file = fopen(filename.c_str(), "w+b");
if (file) {
fwrite(resultArr, length, 1, file);
fclose(file);
}
else {
cout << "open failed";
}
break;
}
else {
writeAndImmediateRead(rfc959::TYPE_I);
controlSocket->write(rfc959::RETRIVE(filename));
string result = controlSocket->read();
cout << result;
int reply = Parser::firstDigit(result);
// I'll remove incomplete local file if request fails
if (reply != rfc959::POSITIVE_PRELIMINARY_REPLY) {
remove(filename.c_str());
continue;
}
wait(NULL);
cout << controlSocket->long_read();
}
}
}
EDIT
I did make sure to use Binary mode. And when I transferred a text file(though of a smaller size), it doesn't have this problem. Here's the output:
EDIT 2
Output from Wireshark showing Request: TYPE I and Response: Opening BINARY mode
By default, FTP servers and clients perform data transfers as "ASCII mode", which means that any CRLF sequence is translated on-the-fly to the host's ASCII line ending (e.g. just bare LF on Unix mmachines). This behavior is mandated by RFC 959; see Section 3.1.1.1.
To transfer your data as binary, and avoid the ASCII mode translation, your FTP client will want to send the TYPE command first, e.g.:
TYPE I
Your .gif file should then be transferred as is, with no replacements/transformations on any CRLF sequences.
Hope this helps!
I am a beginner programmer trying to inflate text stream from pdfs. I have adopted and slightly altered some open source code which uses zlib, and generally it works very well. However, I have been testing on some different pdfs lately and some of the inflated streams are returning blank. Could anybody advise me as to why?
I have come across this question below which seems to address the same problem but does not really give a definitive answer
zLib inflate has empty result in some cases
#include <iostream>
#include <fstream>
#include <string>
#include "zlib.h"
int main()
{
//Discard existing output:
//Open the PDF source file:
std::ifstream filei("C:\\Users\\dpbowe\\Desktop\\PIDSearch\\P&ID.PDF", std::ios::in|std::ios::binary|std::ios::ate);
if (!filei) std::cout << "Error Opening Input File" << std::endl;
//decoded output
std::ofstream fileo;
fileo.open("C:\\Users\\dpbowe\\Desktop\\Decoded.txt", std::ios::binary | std::ofstream::out);
if (!fileother) std::cout << "Error opening output file" << std::endl;
if (filei && fileo)
{
//Get the file length:
long filelen = filei.tellg(); //fseek==0 if ok
filei.seekg(0, std::ios::beg);
//Read the entire file into memory (!):
char* buffer = new char [filelen];
if (buffer == NULL) {fputs("Memory error", stderr); exit(EXIT_FAILURE);}
filei.read(buffer,filelen);
if (buffer == '\0') {fputs("Reading error", stderr); exit(EXIT_FAILURE);}
bool morestreams = true;
//Now search the buffer repeated for streams of data
while (morestreams)
{
//Search for stream, endstream. Should check the filter of the object to make sure it if FlateDecode, but skip that for now!
size_t streamstart = FindStringInBuffer (buffer, "stream", filelen); //This is my own search function
size_t streamend = FindStringInBuffer (buffer, "endstream", filelen); //This is my own search function
if (streamstart>0 && streamend>streamstart)
{
//Skip to beginning and end of the data stream:
streamstart += 6;
if (buffer[streamstart]==0x0d && buffer[streamstart+1]==0x0a) streamstart+=2;
else if (buffer[streamstart]==0x0a) streamstart++;
if (buffer[streamend-2]==0x0d && buffer[streamend-1]==0x0a) streamend-=2;
else if (buffer[streamend-1]==0x0a) streamend--;
//Assume output will fit into 10 times input buffer:
size_t outsize = (streamend - streamstart)*10;
char* output = new char [outsize]; ZeroMemory(output, outsize);
//Now use zlib to inflate:
z_stream zstrm; ZeroMemory(&zstrm, sizeof(zstrm));
zstrm.avail_in = streamend - streamstart + 1;
zstrm.avail_out = outsize;
zstrm.next_in = (Bytef*)(buffer + streamstart);
zstrm.next_out = (Bytef*)output;
int rsti = inflateInit(&zstrm);
if (rsti == Z_OK)
{
int rst2 = inflate (&zstrm, Z_FINISH);
if (rst2 >= 0)
{
size_t totout = zstrm.total_out;
//Write inflated output to file "Decoded.txt"
fileother<<output;
fileother<<"\r\nStream End\r\n\r\n";
}
else std::cout<<"output uncompressed stream is blank"<<std::endl;
}
delete[] output; output=0;
buffer+= streamend + 7;
filelen = filelen - (streamend+7);
}
else
{
morestreams = false;
std::cout<<"End of File"<<std::endl;
}
}
filei.close();
}
else
{
std::cout << "File Could Not Be Accessed\n";
}
if (fileo) fileo.close();
}
I'm sending a request to get an image from a webpage.
I'm using C++ with winsocket and tcp http get request.
I receive all the info in my char buffer but when I stream it to a file or string it's very short because there are string terminators in it.
What's the best/most efficient way to deal with the escape characters?
Thanks in advance.
EDIT:
ofstream out("temp.jpg");
//m_Received.reserve(STRINGBUFFERSIZE);
char rBuffer[BUFFERSIZE];
int readSize = 0;
int totalSize = 0;
do
{
PRINT("Reset buffer");
ZeroMemory(rBuffer, sizeof(rBuffer));
PRINT("Receiving...");
readSize = recv(socket, rBuffer, sizeof(rBuffer), 0);
PRINT("Received " << readSize << " bytes...");
if (readSize > 0)
{
totalSize += readSize;
//m_Received.append(rBuffer);
for (int i = 0; i < readSize; ++i)
{
out << rBuffer[i];
}
if (readSize < BUFFERSIZE)
{
PRINT("Stopping receiving...");
break;
}
}
else if (readSize == -1)
throw SocketError("ErrorReceiving", readSize);
} while (readSize > 0);
Even when putting each character in individually I get a small difference with the original image which leads to corruption.
Alright, so I fixed it by simply writing the char buffer binary to my file. Should have thought of this earlier.
I'm developing a server-client application using Winsock in c++ and have a problem.
For getting the message from the client by the server I use the code below.
int result;
char buffer[200];
while (true)
{
result = recv(client, buffer, 200, NULL);
if (result > 0)
cout << "\n\tMessage from client: \n\n\t" << message << ";";
}
I send the message "Hello" from the client to the server. However the buffer is actually this:
HelloÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ
What am I missing?
Since recv might not receive as many bytes as you told it, you typically use a function
like this to receive specified number of bytes. Modified from here
int receiveall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've received
int bytesleft = *len; // how many we have left to receive
int n = -1;
while(total < *len) {
n = recv(s, buf+total, bytesleft, 0);
if (n <= 0) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually received here
return (n<=0)?-1:0; // return -1 on failure, 0 on success
}
It's up to you to null terminate the string if you receive string which is not null terminated.
The result tells you how many bytes were received. recv doesn't add a terminator since, in general, network data is binary data which might not be usable as a C-style string.
You can add a terminator yourself, if you know the message won't contain the termination character:
buffer[result] = 0; // make sure the buffer is large enough
or make a string (or vector, or whatever) from it:
std::string message_str(message, result);
Note that what you receive might not be a single "message", especially if you're uses a stream protocol like TCP. It might contain more than one message, or just the start of one.
memset(&receive[0], 0, sizeof(receive));
To clear the buffer
You didn't initialize your buffer
char buffer[200] = {0};
while (true)
{
result = recv(client, buffer, 200, NULL);
if (result > 0)
cout << "\n\tMessage from client: \n\n\t" << message << ";";
memset(buffer, 0, 200);
}
what is the right way to read chunked data (from http request) from socket?
sf::TcpSocket socket;
socket.connect("0.0.0.0", 80);
std::string message = "GET /address HTTP/1.1\r\n";
socket.send(message.c_str(), message.size() + 1);
// Receive an answer from the server
char buffer[128];
std::size_t received = 0;
socket.receive(buffer, sizeof(buffer), received);
std::cout << "The server said: " << buffer << std::endl;
But server sends infinite data and socket.receive doesn't return management. Any right ways to read chunked data part by part? (The answer is chunked data).
The right way to process HTTP requests is to use a higher-level library that manages the socket connections for you. In C++ one example would be pion-net; there are others too like Mongoose (which is C, but fine to use in C++).
Well infinite data is theoretically possible while the practical implementation differ from process to process.
Approach 1 - Generally many protocol do send size in the first few bytes ( 4 bytes ) and you can have a while loop
{
int i = 0, ret = 1;
unsigned char buffer[4];
while ( i<4 && ret == 0)
socket.receive(buffer + i, 1 , ret);
// have a while loop to read the amount of data you need. Malloc the buffer accordingly
}
Approach 2 - Or in your case where you don't know the lenght ( infinite )
{
char *buffer = (char *)malloc(TCP_MAX_BUF_SIZE);
std::size_t total = 0, received = 0;
while ( total < TCP_MAX_BUF_SIZE && return >= 0) {
socket.receive(buffer, sizeof(buffer), received);
total += received;
}
//do something with your data
}
You will have to break at somepoint and process your data Dispatch it to another thread of release the memory.
If by "chunked data" you are referring to the Transfer-Encoding: chunked HTTP header, then you need to read each chunk and parse the chunk headers to know how much data to read in each chunk and to know when the last chunk has been received. You cannot just blindly call socket.receive(), as chunked data has a defined structure to it. Read RFC 2616 Section 3.6.1 for more details.
You need to do something more like the following (error handling omitted for brevity - DON'T omit it in your real code):
std::string ReadALine(sf::TcpSocket &socket)
{
std::string result;
// read from socket until a LF is encountered, then
// return everything up to, but not including, the
// LF, stripping off CR if one is also present...
return result;
}
void ReadHeaders(sf::TcpSocket &socket, std::vector<std::string> &headers)
{
std::string line;
do
{
line = ReadALine(socket);
if (line.empty()) return;
headers.push_back(line);
}
while (true);
}
std::string UpperCase(const std::string &s)
{
std::string result = s;
std::for_each(result.begin(), result.end(), toupper);
return result;
}
std::string GetHeader(const std::vector<std::string> &headers, const std::string &s)
{
std::string prefix = UpperCase(s) + ":";
for (std::vector<std::string>::iterator iter = headers.begin(), end = headers.end(); iter != end; ++iter)
{
if (UpperCase(i)->compare(0, prefix.length(), prefix) == 0)
return i->substr(prefix.length());
}
return std::string();
}
sf::TcpSocket socket;
socket.connect("0.0.0.0", 80);
std::string message = "GET /address HTTP/1.1\r\nHost: localhost\r\n\r\n";
socket.send(message.c_str(), message.length());
std:vector<std::string> headers;
std::string statusLine = ReadALine(sockeet);
ReadHeaders(socket, headers);
// Refer to RFC 2616 Section 4.4 for details about how to properly
// read a response body in different situations...
int statusCode;
sscanf(statusLine.c_str(), "HTTP/%*d.%*d %d %*s", &statusCode);
if (
((statusCode / 100) != 1) &&
(statusCode != 204) &&
(statusCode != 304)
)
{
std::string header = GetHeader(headers, "Transfer-Encoding");
if (UpperCase(header).find("CHUNKED") != std::string::npos)
{
std::string extensions;
std::string_size_type pos;
std::size_t chunkSize;
do
{
line = ReadALine(socket);
pos = line.find(";");
if (pos != std::string::npos)
{
extensions = line.substr(pos+1);
line.resize(pos);
}
else
extensions.clear();
chunkSize = 0;
sscanf(UpperCase(line).c_str(), "%X", &chunkSize);
if (chunkSize == 0)
break;
socket.receive(someBuffer, chunkSize);
ReadALine(socket);
// process extensions as needed...
// copy someBuffer into your real buffer...
}
while (true);
std::vector<std::string> trailer;
ReadHeaders(socket, trailer);
// merge trailer into main header...
}
else
{
header = GetHeader(headers, "Content-Length");
if (!header.empty())
{
uint64_t contentLength = 0;
sscanf(header.c_str(), "%Lu", &contentLength);
// read from socket until contentLength number of bytes have been read...
}
else
{
// read from socket until disconnected...
}
}
}