diff mbox

[13/17] Add http-server.h and http-server.c

Message ID 1500926714-56988-14-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm July 24, 2017, 8:05 p.m. UTC
This patch implements an abstract class of HTTP server, as a subclass
of the server class implemented in the previous patch.

gcc/ChangeLog:
	* Makefile.in (OBJS): Add http-server.o.
	* http-server.c: New file.
	* http-server.h: New file.
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::http_server_c_tests.
	* selftest.h (selftest::http_server_c_tests): New decl.
---
 gcc/Makefile.in          |   1 +
 gcc/http-server.c        | 358 +++++++++++++++++++++++++++++++++++++++++++++++
 gcc/http-server.h        | 101 +++++++++++++
 gcc/selftest-run-tests.c |   1 +
 gcc/selftest.h           |   1 +
 5 files changed, 462 insertions(+)
 create mode 100644 gcc/http-server.c
 create mode 100644 gcc/http-server.h
diff mbox

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 4e60bc0..0c361f1 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1332,6 +1332,7 @@  OBJS = \
 	hsa-regalloc.o \
 	hsa-brig.o \
 	hsa-dump.o \
+	http-server.o \
 	hw-doloop.o \
 	hwint.o \
 	ifcvt.o \
diff --git a/gcc/http-server.c b/gcc/http-server.c
new file mode 100644
index 0000000..88c1c17
--- /dev/null
+++ b/gcc/http-server.c
@@ -0,0 +1,358 @@ 
+/* HTTP server implementation.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "http-server.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "diagnostic.h"
+
+/* class http::message.  */
+
+/* Set the content of this message.  */
+
+void
+http::message::set_content (size_t length, const char *content)
+{
+  m_length = length;
+  m_buffer = xstrndup (content, length);
+}
+
+/* class http::request : public http::message.  */
+
+/* http::request's constructor.  */
+
+http::request::request ()
+: message (), m_parsing_body (false), m_pending_data (),
+  m_content_length (-1), m_header_map ()
+{
+}
+
+/* http::request's destructor.  */
+
+http::request::~request()
+{
+  for (header_map_t::iterator it = m_header_map.begin ();
+       it != m_header_map.end (); ++it)
+    {
+      free (const_cast <char *>((*it).first));
+      free (const_cast <char *>((*it).second));
+    }
+}
+
+/* Access the given header within this request, or NULL if not present.  */
+
+const char *
+http::request::get_header (const char *header) const
+{
+  char **slot = const_cast <request*> (this)->m_header_map.get (header);
+  if (slot)
+    return *slot;
+  return NULL;
+}
+
+/* Consume up to LENGTH bytes from BUF.
+   Return the number of bytes consumed.  */
+
+size_t
+http::request::parse_buffer (size_t length, const char *buf)
+{
+  size_t idx;
+  for (idx = 0; idx < length; idx++)
+    {
+      if (consume_octet (buf[idx]))
+	return idx + 1;
+    }
+  return idx;
+}
+
+/* Parse CH.  Return true if the request has finished parsing, or false
+   if more data is expected.  */
+
+bool
+http::request::consume_octet (char ch)
+{
+  if (m_parsing_body)
+    return consume_body_octet (ch);
+  else
+    return consume_header_octet (ch);
+}
+
+/* Parse CH within the headers.  Return true if the request has finished
+   parsing, or false if more data is expected.  */
+
+bool
+http::request::consume_header_octet (char ch)
+{
+  /* We're parsing the headers.  */
+  m_pending_data.safe_push (ch);
+  /* Detect "\r\n".  */
+  size_t len = m_pending_data.length ();
+  if (len >= 2 && m_pending_data[len - 2] == '\r' && ch == '\n')
+    {
+      /* Is this a blank line?  If so, then we're done with headers.  */
+      if (len == 2)
+	{
+	  m_pending_data.truncate (0);
+	  const char *content_length_str = get_header ("Content-Length");
+	  if (content_length_str)
+	    {
+	      m_content_length = atoi (content_length_str);
+	      // FIXME: error-handling for non-int values
+	      /* We're not yet finished; we must read the body.  */
+	      m_parsing_body = true;
+	      return false;
+	    }
+	  else
+	    /* If there was no Content-Length, we have nothing
+	       more to read.  */
+	    return true;
+	}
+      else
+	{
+	  /* Otherwise we have a header (or the initial verb line).  */
+	  parse_header (m_pending_data.length () - 2, &m_pending_data[0]);
+	  m_pending_data.truncate (0);
+	  /* We're not yet finished.  */
+	  return false;
+	}
+    }
+
+  /* Not yet finished.  */
+  return false;
+}
+
+/* Parse CH within the body.  Return true if the request has finished
+   parsing, or false if more data is expected.  */
+
+bool
+http::request::consume_body_octet (char ch)
+{
+  /* Accumulate data until we've seen Content-Length octets.  */
+  gcc_assert (m_content_length > 0);
+  m_pending_data.safe_push (ch);
+  if (m_pending_data.length () == m_content_length)
+    {
+      set_content (m_content_length, &m_pending_data[0]);
+      m_pending_data.truncate (0);
+      /* We've finished parsing this request.  */
+      return true;
+    }
+  else
+    /* We're not yet finished.  */
+    return false;
+}
+
+/* FIXME.  */
+
+void
+http::request::parse_header (size_t length, const char *buf)
+{
+  /* header-field   = field-name ":" OWS field-value OWS  */
+  for (size_t colon_idx = 0; colon_idx + 1 < length; colon_idx++)
+    // FIXME: whitespace after colon is optional
+    // FIXME: optional trailing whitespace after header value
+    if (buf[colon_idx] == ':' && buf[colon_idx + 1] == ' ')
+      {
+	char *key = xstrndup (buf, colon_idx);
+	char *value = xstrndup (buf + colon_idx + 2,
+				length - (colon_idx + 2));
+	m_header_map.put (key, value);
+	return;
+      }
+  // FIXME: error-handling
+}
+
+/* class http::response : public http::message.  */
+
+/* Generate a string form of this response.
+   The caller is responsible for freeing it.  */
+
+char *
+http::response::to_str () const
+{
+  pretty_printer pp;
+  pp_string (&pp, "HTTP/1.1 200 OK\r\n");
+  pp_printf (&pp, "Content-Length: %i\r\n", (int)get_content_length ());
+  pp_string (&pp, "\r\n");
+  if (get_content ())
+    pp_string (&pp, get_content ());
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+/* Implementation of ::server::on_read for http::server.
+   Read up to LENGTH bytes from BUF, and potentially call
+   on_http_request for any requests seen.
+   Write any responses back to FD.  */
+
+void
+http::server::on_read (file_descriptor fd, size_t length, const char *buf)
+{
+  if (m_verbose)
+    inform (UNKNOWN_LOCATION, "received http request: %qs",
+	    buf); // FIXME respect length
+
+  /* FIXME: this assumes we have a full request i.e. two "\r\n\r\n"
+     If we don't we should read more until we do.  */
+  size_t req_start = 0;
+  while (req_start < length)
+    {
+      request req;
+      req_start += req.parse_buffer (length, buf);
+      http::response resp;
+      on_http_request (req, resp);
+      char *resp_str = resp.to_str ();
+      if (1)
+	inform (UNKNOWN_LOCATION, "sending http response: %qs",
+		resp_str);
+      write (fd.m_fd, resp_str, strlen (resp_str));
+      free (resp_str);
+    }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests.  */
+
+/* Verify that we can parse an HTTP request.  */
+
+static void
+test_parse_request ()
+{
+  const char *in = ("POST /jsonrpc HTTP/1.1\r\n"
+		    "Host: localhost:4000\r\n"
+		    "Content-Length: 12\r\n"
+		    "content-type: application/json\r\n"
+		    "Accept-Encoding: gzip, deflate, compress\r\n"
+		    "Accept: */*\r\n"
+		    "User-Agent: test-user-agent\r\n"
+		    "\r\n"
+		    "test-content");
+  http::request r;
+  size_t consumed = r.parse_buffer (strlen (in), in);
+  ASSERT_EQ (consumed, strlen (in));
+  ASSERT_STREQ ("test-user-agent", r.get_header ("User-Agent"));
+  ASSERT_STREQ ("12", r.get_header ("Content-Length"));
+  ASSERT_EQ (NULL, r.get_header ("Not-A-Header"));
+  ASSERT_EQ (12, r.get_content_length ());
+  ASSERT_TRUE (0 == strncmp ("test-content", r.get_content (), 12));
+}
+
+/* Verify that we can split up the parsing of a request at arbitrary
+   places.  */
+
+static void
+test_parse_split_request ()
+{
+  const char *in = ("POST /jsonrpc HTTP/1.1\r\n"
+		    "Host: localhost:4000\r\n"
+		    "Content-Length: 12\r\n"
+		    "content-type: application/json\r\n"
+		    "Accept-Encoding: gzip, deflate, compress\r\n"
+		    "Accept: */*\r\n"
+		    "User-Agent: test-user-agent\r\n"
+		    "\r\n"
+		    "test-content");
+  for (size_t split = 0; split < strlen (in); split++)
+    {
+      http::request r;
+
+      size_t consumed_1 = r.parse_buffer (split, in);
+      ASSERT_EQ (consumed_1, split);
+
+      size_t consumed_2 = r.parse_buffer (strlen (in) - split, in + split);
+      ASSERT_EQ (consumed_2, strlen (in) - split);
+
+      ASSERT_STREQ ("test-user-agent", r.get_header ("User-Agent"));
+      ASSERT_STREQ ("12", r.get_header ("Content-Length"));
+      ASSERT_EQ (NULL, r.get_header ("Not-A-Header"));
+      ASSERT_EQ (12, r.get_content_length ());
+      ASSERT_TRUE (0 == strncmp ("test-content", r.get_content (), 12));
+    }
+}
+
+/* Verify that we can parse multiple requests out of one buffer,
+   honoring the Content-Length headers.  */
+
+static void
+test_parse_multiple_requests ()
+{
+  const char *in = ("POST /test HTTP/1.1\r\n"
+		    "Content-Length: 25\r\n"
+		    "\r\n"
+		    "This is the first request"
+		    "POST /test HTTP/1.1\r\n"
+		    "Content-Length: 26\r\n"
+		    "\r\n"
+		    "This is the second request");
+  http::request r1;
+  size_t consumed_1 = r1.parse_buffer (strlen (in), in);
+  ASSERT_EQ (68, consumed_1);
+  ASSERT_STREQ ("25", r1.get_header ("Content-Length"));
+  ASSERT_EQ (25, r1.get_content_length ());
+  ASSERT_TRUE (0 == strncmp ("This is the first request",
+			     r1.get_content (), 25));
+
+  http::request r2;
+  size_t consumed_2 = r2.parse_buffer (strlen (in) - consumed_1,
+				       in + consumed_1);
+  ASSERT_EQ (69, consumed_2);
+  ASSERT_STREQ ("26", r2.get_header ("Content-Length"));
+  ASSERT_EQ (26, r2.get_content_length ());
+  ASSERT_TRUE (0 == strncmp ("This is the second request",
+			     r2.get_content (), 26));
+
+  ASSERT_EQ (strlen (in), consumed_1 + consumed_2);
+}
+
+/* Verify http::response::to_str.  */
+
+static void
+test_emit_response ()
+{
+  http::response r;
+  const char *msg = "hello world";
+  r.set_content (strlen (msg), msg);
+
+  char *str = r.to_str ();
+  ASSERT_STREQ ("HTTP/1.1 200 OK\r\n"
+		"Content-Length: 11\r\n"
+		"\r\n"
+		"hello world", str);
+  free (str);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+http_server_c_tests ()
+{
+  test_parse_request ();
+  test_parse_split_request ();
+  test_parse_multiple_requests ();
+  test_emit_response ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/http-server.h b/gcc/http-server.h
new file mode 100644
index 0000000..25d542d
--- /dev/null
+++ b/gcc/http-server.h
@@ -0,0 +1,101 @@ 
+/* HTTP server implementation.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_HTTP_SERVER_H
+#define GCC_HTTP_SERVER_H
+
+#include "server.h"
+
+namespace http {
+
+/* http::message is a base class for encapsulating a collection of
+   octets received or to be sent.  */
+
+class message
+{
+ public:
+  message () : m_length (0), m_buffer (NULL) {}
+  ~message () { free (m_buffer); }
+
+  size_t get_content_length () const { return m_length; }
+  const char *get_content () const { return m_buffer; }
+
+  void set_content (size_t length, const char *content);
+
+ private:
+  size_t m_length;
+  char *m_buffer;
+};
+
+/* http::request encapsulates an HTTP request.  */
+
+class request : public message
+{
+ public:
+  request ();
+  ~request();
+
+  const char *get_header (const char *header) const;
+  size_t parse_buffer (size_t length, const char *buf);
+
+ private:
+  bool consume_octet (char ch);
+  bool consume_header_octet (char ch);
+  bool consume_body_octet (char ch);
+  void parse_header (size_t sz, const char *buf);
+
+  bool m_parsing_body;
+  auto_vec<char> m_pending_data;
+  size_t m_content_length;
+
+  typedef hash_map <char *, char *,
+    simple_hashmap_traits<nofree_string_hash, char *> > header_map_t;
+  header_map_t m_header_map;
+};
+
+/* http::response encapsulates an HTTP response.  */
+
+class response : public message
+{
+ public:
+  char *to_str () const;
+};
+
+/* Subclass of ::server than expects an HTTP-like protocol,
+   with header lines ended by '\r\n', then a '\r\n' line, then
+   the content.  */
+
+class server : public ::server
+{
+ public:
+  server (bool verbose) : m_verbose (verbose) {}
+
+  void on_read (file_descriptor fd, size_t length,
+		const char *buf) OVERRIDE FINAL;
+
+  virtual void on_http_request (const http::request &request,
+				http::response &response) = 0;
+
+ private:
+  bool m_verbose;
+};
+
+} // namespace http
+
+#endif  /* GCC_HTTP_SERVER_H  */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index a8668f4..7e75680 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -68,6 +68,7 @@  selftest::run_tests ()
   typed_splay_tree_c_tests ();
   blt_c_tests ();
   json_c_tests ();
+  http_server_c_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 281658b..ad4e957 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -183,6 +183,7 @@  extern void gimple_c_tests ();
 extern void ggc_tests_c_tests ();
 extern void hash_map_tests_c_tests ();
 extern void hash_set_tests_c_tests ();
+extern void http_server_c_tests ();
 extern void input_c_tests ();
 extern void json_c_tests ();
 extern void pretty_print_c_tests ();