File:  [parser3project] / parser3 / src / targets / cgi / pa_sapi_info.h
Revision 1.22: download - view: text, annotated - select for diffs - revision graph
Mon May 26 00:52:15 2025 UTC (13 months, 1 week ago) by moko
Branches: MAIN
CVS tags: HEAD
implements feature #1277
String::Body(const char *abody) now explicit
but HashString::put(const char *str, V value) added for simplicity
optimizations:
HASH_PUT_CSTR used where hash.put called several time during execution;
caching String::Body::uitoa used at all pash.put(<uint> calls.

#ifndef PA_SAPI_INFO_H
#define PA_SAPI_INFO_H

#define IDENT_PA_SAPI_INFO_H "$Id: pa_sapi_info.h,v 1.22 2025/05/26 00:52:15 moko Exp $"

#include "pa_sapi.h"
#include "pa_http.h"

/// IIS refuses to read bigger chunks
const size_t READ_POST_CHUNK_SIZE=0x400*0x400; // 1M
const int HEADERS_SENT = 999; // can't send error message after headers have been sent

// for signal handlers and cgi console detection
static Request *request=0;


class SAPI_Info : public PA_Allocated {
public:
	int http_response_code;
	String::Body headers;

	SAPI_Info() : http_response_code(200) {}

	virtual char* get_env(const char* name) {
		if(char *local=getenv(name))
			return pa_strdup(local);
		else
			return 0;
	}

	virtual bool set_env(const char*, const char*) {
		return false;
	}

	virtual const char* const *get_env() {
#ifdef _MSC_VER
		extern char **_environ;
		return _environ;
#else
		extern char **environ;
		return environ;
#endif
	}

	virtual size_t read_post(char *, size_t) {
		return 0;
	}

	virtual void add_header(const char* dont_store_key, const char* dont_store_value) {
		if(strcasecmp(dont_store_key, "location")==0)
			http_response_code=302;
		if(strcasecmp(dont_store_key, HTTP_STATUS)==0)
			http_response_code=atoi(dont_store_value);
	}

	virtual void send_headers() {}

	void clear_headers() {
		http_response_code=200;
		headers.clear();
	}

	virtual size_t send_body(const void *buf, size_t size) {
		return stdout_write(buf, size);
	}

	virtual void send_error(const char *exception_cstr, const char *status){
		http_response_code=atoi(status);
		send_body(exception_cstr, strlen(exception_cstr));
	}
};

class SAPI_Info_CGI : public SAPI_Info {
public:

	virtual size_t read_post(char *buf, size_t max_bytes) {
		size_t read_size=0;
		do {
			ssize_t chunk_size=read(fileno(stdin), buf+read_size, min(READ_POST_CHUNK_SIZE, max_bytes-read_size));
			if(chunk_size<=0)
			break;
			read_size+=chunk_size;
		} while(read_size<max_bytes);
		return read_size;
	}

	virtual void add_header(const char* dont_store_key, const char* dont_store_value) {
		headers << capitalize(dont_store_key) << ": " << pa_strdup(dont_store_value) << "\r\n";
	}

	virtual void send_headers() {
		if(!request || !request->console.was_used()){
			headers << "\r\n";
			send_body(headers.cstr(), headers.length());
			http_response_code=HEADERS_SENT;
		}
	}

	virtual void send_error(const char *exception_cstr, const char *status){
		if (http_response_code==HEADERS_SENT)
			return;
		// memory allocation is not allowed
		char buf[MAX_STRING];
		snprintf(buf, MAX_STRING, HTTP_STATUS_CAPITALIZED ": %s\r\n"
				HTTP_CONTENT_TYPE_CAPITALIZED ": text/plain\r\n\r\n", status);
		send_body(buf, strlen(buf));
		send_body(exception_cstr, strlen(exception_cstr));
	}

};

static char* replace_char(char* str, char from, char to){
    for(char *pos = strchr(str,from); pos; pos=strchr(pos,from)) {
        *pos = to;
    }
    return str;
}

class SAPI_Info_HTTPD : public SAPI_Info {
public:

	HTTPD_Connection &connection;
	HashStringString env;

	SAPI_Info_HTTPD(HTTPD_Connection &aconnection) : connection(aconnection) {}

	void populate_env() {
		String::Body host("localhost");
		for(Array_iterator<HTTP_Headers::Header> i(connection.headers()); i; ){
			HTTP_Headers::Header header=i.next();
			String name("HTTP_");
			name << replace_char(header.name.cstrm(), '-', '_');
			String::Body value=header.value;

			if(header.name == "HOST"){
				size_t port=value.pos(':');
				if(port != STRING_NOT_FOUND)
					value=value.mid(0, port);
				host=value;
			}
			env.put(name, value);
		}

		HASH_PUT_CSTR(env, "REQUEST_METHOD", String::Body(connection.method()));
		HASH_PUT_CSTR(env, "REQUEST_URI", String::Body(connection.uri()));
		HASH_PUT_CSTR(env, "QUERY_STRING", String::Body(connection.query()));

		HASH_PUT_CSTR(env, "SERVER_NAME", host);
		HASH_PUT_CSTR(env, "SERVER_PORT", String::Body(HTTPD_Server::port));
		HASH_PUT_CSTR(env, "REMOTE_ADDR", String::Body(connection.remote_addr));

	}

	virtual char* get_env(const char* name) {
		String::Body request_value = env.get(name);
		if(!request_value.is_empty())
			return request_value.cstrm();
		if(char *server_value=getenv(name))
			return pa_strdup(server_value);
		return NULL;
	}

	virtual bool set_env(const char* name, const char* value) {
		env.put(name, *new String(value));
		return true;
	}

	virtual const char* const *get_env() {
		const char** result=new(PointerGC) const char*[env.count()+1/*0*/];
		const char** cur=result;
		for(HashStringString::Iterator i(env); i; i.next()){
			String pair;
			pair << i.key() << "=" << i.value();
			*cur++=pair.cstr();
		}
		*cur=NULL;
		return result;
	}

	virtual size_t read_post(char *buf, size_t max_bytes) {
		return connection.read_post(buf, max_bytes);
	}

	static const char *exception_http_status(const char *type) {
		struct Lookup {
			const char *code;
			const char *type;
		} static lookup[] = {
			{  "",  "httpd.write"},
			{"400", "httpd.request"},
			{"400", "http.response"},
			{"404", "file.missing"},
			{"408", "httpd.timeout"},
			{"408", "httpd.read"},
			{"501", "httpd.method"},
			{ NULL, ""}
		};
		Lookup *cur = lookup;
		for(; cur->code; cur++)
			if(!strcmp(type, cur->type))
				return cur->code;
		return "500";
	}

	static const char *status_message(int code) {
		struct Lookup {
			int code;
			const char *message;
		} static lookup[] = {
			{200, "OK"},
			{204, "No Content"},
			{206, "Partial Content"},
			{301, "Moved Permanently"},
			{302, "Found"},
			{304, "Not Modified"},
			{400, "Bad Request"},
			{401, "Unauthorized"},
			{403, "Forbidden"},
			{404, "Not Found"},
			{408, "Request Timeout"},
			{416, "Range Not Satisfiable"},
			{500, "Internal Server Error"},
			{501, "Not Implemented"},
			{502, "Bad Gateway"},
			{504, "Gateway Timeout"},
			{  0, "Undescribed"}
		};
		Lookup *cur = lookup;
		for(; cur->code; cur++)
			if(code == cur->code)
				return cur->message;
		return cur->message;
	}

	virtual void add_header(const char* dont_store_key, const char* dont_store_value) {
		if(strcasecmp(dont_store_key, "location")==0)
			http_response_code=302;
		if(strcasecmp(dont_store_key, HTTP_STATUS)==0)
			http_response_code=atoi(dont_store_value);
		else
			headers << capitalize(dont_store_key) << ": " << pa_strdup(dont_store_value) << "\r\n";
	}

	virtual void send_headers() {
		String result("HTTP/1.0 ");
		result << pa_uitoa(http_response_code) << " " << status_message(http_response_code) << "\r\n" << headers << "\r\n";
		send_body(result.cstr(), result.length());
		http_response_code=HEADERS_SENT;
	}

	virtual size_t send_body(const void *buf, size_t size) {
		return connection.send_body(buf, size);
	}

	virtual void send_error(const char *exception_cstr, const char *status){
		if (http_response_code==HEADERS_SENT)
			return;
		// memory allocation is not allowed
		char buf[MAX_STRING];
		snprintf(buf, MAX_STRING, "HTTP/1.0 %s %s\r\n"
				HTTP_CONTENT_TYPE_CAPITALIZED ": text/plain\r\n\r\n", status, status_message(atoi(status)));
		send_body(buf, strlen(buf));
		send_body(exception_cstr, strlen(exception_cstr));
	}

};

#endif

E-mail: