mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-03 11:15:20 +02:00
RequestHeader::GetParamValue indexed parts[1] without checking the size. A query
parameter with no '=' at all ("?foo") makes SplitString return a single element, so
both the DEBUG_LOG and the assignment read off the end of the vector. Nothing calls
GetParamValue today, so this is latent rather than live, but it's driven straight
off the request line.
The 64-bit frame length was assembled with header[n] << 24 on uint8_t values, which
promote to int - a byte >= 0x80 in the top position shifts into the sign bit and then
sign-extends when widened to uint64_t. The resulting size was always rejected, just
by the wrong check and via signed overflow to get there. Cast first.
OutputSink::Block() had the same shape as the InputSink one this branch already
fixed: a broken socket is reported ready immediately and forever, so waiting on it
is a spin. Bail if the sink already knows it's broken.
178 lines
4.5 KiB
C++
178 lines
4.5 KiB
C++
#include "Common/Net/HTTPHeaders.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <algorithm> // std::transform
|
|
|
|
#include "Common/Net/Sinks.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/File/FileDescriptor.h"
|
|
#include "Common/StringUtils.h"
|
|
|
|
namespace http {
|
|
|
|
RequestHeader::RequestHeader() {
|
|
}
|
|
|
|
RequestHeader::~RequestHeader() {
|
|
delete [] referer;
|
|
delete [] user_agent;
|
|
delete [] resource;
|
|
delete [] params;
|
|
}
|
|
|
|
bool RequestHeader::GetParamValue(const char *param_name, std::string *value) const {
|
|
if (!params)
|
|
return false;
|
|
std::string p(params);
|
|
std::vector<std::string_view> v;
|
|
SplitString(p, '&', v);
|
|
for (size_t i = 0; i < v.size(); i++) {
|
|
std::vector<std::string_view> parts;
|
|
SplitString(v[i], '=', parts);
|
|
if (parts.size() < 2) {
|
|
// A parameter with no '=' at all, like "?foo". SplitString hands back a single element
|
|
// for that, so parts[1] would be reading off the end of the vector.
|
|
continue;
|
|
}
|
|
DEBUG_LOG(Log::HTTP, "Param: %.*s Value: %.*s", (int)parts[0].size(), parts[0].data(), (int)parts[1].size(), parts[1].data());
|
|
if (parts[0] == param_name) {
|
|
*value = parts[1];
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RequestHeader::GetOther(const char *name, std::string *value) const {
|
|
auto it = other.find(name);
|
|
if (it != other.end()) {
|
|
*value = it->second;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int RequestHeader::ParseHttpHeader(const char *buffer) {
|
|
if (first_header_) {
|
|
// Step 1: Method
|
|
first_header_ = false;
|
|
if (!memcmp(buffer, "GET ", 4)) {
|
|
method = GET;
|
|
buffer += 4;
|
|
} else if (!memcmp(buffer, "HEAD ", 5)) {
|
|
method = HEAD;
|
|
buffer += 5;
|
|
} else if (!memcmp(buffer, "POST ", 5)) {
|
|
method = POST;
|
|
buffer += 5;
|
|
} else {
|
|
method = UNSUPPORTED;
|
|
status = 405;
|
|
return -1;
|
|
}
|
|
SkipSpace(&buffer);
|
|
|
|
// Step 2: Resource, params (what's after the ?, if any)
|
|
const char *endptr = strchr(buffer, ' ');
|
|
const char *q_ptr = strchr(buffer, '?');
|
|
if (!endptr) {
|
|
// No trailing space. Fall back to
|
|
// the end of the line instead of leaving endptr null.
|
|
// Nemo's repro (python):
|
|
// import socket
|
|
// SERVER_IP = "127.0.0.1"
|
|
// PORT = 65199 # my port
|
|
// message = "GET /\n"
|
|
// client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
// client_socket.connect((SERVER_IP, PORT)); client_socket.sendall(message.encode('utf-8'))
|
|
endptr = buffer + strlen(buffer);
|
|
}
|
|
|
|
int resource_name_len;
|
|
if (q_ptr)
|
|
resource_name_len = (int)(q_ptr - buffer);
|
|
else
|
|
resource_name_len = (int)(endptr - buffer);
|
|
if (!resource_name_len) {
|
|
status = 400;
|
|
return -1;
|
|
}
|
|
resource = new char[resource_name_len + 1];
|
|
memcpy(resource, buffer, resource_name_len);
|
|
resource[resource_name_len] = '\0';
|
|
if (q_ptr) {
|
|
int param_length = (int)(endptr - q_ptr - 1);
|
|
if (param_length < 0) // This is likely just paranoia.
|
|
param_length = 0;
|
|
params = new char[param_length + 1];
|
|
memcpy(params, q_ptr + 1, param_length);
|
|
params[param_length] = '\0';
|
|
}
|
|
if (strstr(buffer, "HTTP/"))
|
|
type = FULL;
|
|
else
|
|
type = SIMPLE;
|
|
return 0;
|
|
}
|
|
|
|
// We have a real header to parse.
|
|
const char *colon = strchr(buffer, ':');
|
|
if (!colon) {
|
|
status = 400;
|
|
return -1;
|
|
}
|
|
|
|
// The header is formatted as key: value.
|
|
int key_len = colon - buffer;
|
|
const char *key = buffer;
|
|
|
|
// Go to after the colon to get the value.
|
|
buffer = colon + 1;
|
|
SkipSpace(&buffer);
|
|
int value_len = (int)strlen(buffer);
|
|
|
|
if (!strncasecmp(key, "User-Agent", key_len)) {
|
|
user_agent = new char[value_len + 1];
|
|
memcpy(user_agent, buffer, value_len + 1);
|
|
VERBOSE_LOG(Log::HTTP, "user-agent: %s", user_agent);
|
|
} else if (!strncasecmp(key, "Referer", key_len)) {
|
|
referer = new char[value_len + 1];
|
|
memcpy(referer, buffer, value_len + 1);
|
|
} else if (!strncasecmp(key, "Content-Length", key_len)) {
|
|
content_length = atoi(buffer);
|
|
VERBOSE_LOG(Log::HTTP, "Content-Length: %i", (int)content_length);
|
|
} else {
|
|
std::string key_str(key, key_len);
|
|
std::transform(key_str.begin(), key_str.end(), key_str.begin(), tolower);
|
|
other[key_str] = buffer;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void RequestHeader::ParseHeaders(net::InputSink *sink) {
|
|
int line_count = 0;
|
|
std::string line;
|
|
while (sink->ReadLine(line)) {
|
|
if (line.length() == 0) {
|
|
// Blank line, this means end of headers.
|
|
break;
|
|
}
|
|
|
|
ParseHttpHeader(line.c_str());
|
|
line_count++;
|
|
if (type == SIMPLE) {
|
|
// Done!
|
|
VERBOSE_LOG(Log::HTTP, "Simple: Done parsing http request.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
VERBOSE_LOG(Log::HTTP, "finished parsing request.");
|
|
ok = line_count > 1 && resource != nullptr;
|
|
}
|
|
|
|
} // namespace http
|