C-Scene Issue #07
Jürgen Hermann

CGI Bits & Pieces #2

This is the second installment of CGI Bits & Pieces, which shows you how to parse data submitted from a HTML form and send it as a mail to a predefined email account. If you are a CGI novice, see the references at the end of this article. Having read Brent's article is the bare minimum to understand anything that follows.
All examples are implemented in C++ and usually compiled and tested on both Windows NT4 using Visual C++ 5.0 and FreeBSD/linux using egcs.

Form handling

The data a user inputs into the fields of a form is sent to the URL that is contained in the ACTION property of the FORM tag. But you do not get the data as it is entered, but in a coded form that allows you to dissect control information from user data. The control information is the name of the form fields, so you are able to associate the data with the field it was entered into.
The fields are sent as a string of name=value pairs, and if there are several fields, those pairs are separated by an ampersand "&". Certain chars in the name and value part are www-url-encoded, which means spaces are sent as a "+" character, and all other special characters are sent as a per-cent sign "%" followed by the code of the character as two hex-digits. Obviously, the characters "%&+=" have to be coded in that form; which characters are encoded exactly is the decision of the browser.
The following code echos this encoded form information, in clear text form, into a mail. Additionally, relevant information from the cgi-bin environment is also included in the mail. To prevent people from sending to any email address (i.e. using our server to spam), we have a config file that defines valid destination addresses; this also allows us to define a follow-up URL that is showed to the user after he clicked on the submit button. Both these configuration values are accessed by an alias name that is appended to the URL in the form tag and thus can be found in the QUERY_STRING variable.
Below, you find the HTML code of a sample form, the C++ source code and a sample configuration file. Finally, you can see a mail generated by the program compiled for RedHat Linux 2.0.36 using egcs-2.91.60.
A sample FORM
<form method=post action="/cgi-bin/wwwmail?jh">
<input type=text name=topic><br>
<textarea name=memo cols=60 rows=3></textarea><br>
<input type=submit name=button value=" Send it! ">
</form>
wwwmail.cpp
// Send form data to an email account
//
// Copyright (c) 1999 by Jürgen Hermann, All Rights Reserved.

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <fstream>
#include <sstream>


// this is the return address put into the mail (should be in the config file)
const char* POSTMASTER = "David Stiles <pingme@home.com>";


// get delimited substring from "str" and remove it from "str"
std::string strParse(std::string& str, const char* delim)
{
  std::string result;
  std::string::size_type pos = str.find_first_of(delim);

  if (pos == std::string::npos) {
    result = str;
    str    = "";
  } else {
    result = str.substr(0, pos);
    str.erase(0, pos+1);
  }

  return result;
}


// send an environment value to a stream, if it actually exists
void echoEnv(ostream& out, const char* envvar)
{
  char* val = getenv(envvar);
  if (val) out << envvar << '=' << val << '\n';
}


// the main program
int main()
{
  // init
  mode_t oldmask = umask(0);
  umask(oldmask & ~mode_t(0770));

  // scan the .cfg file and check for allowed destinations
  char* query = getenv("QUERY_STRING");
  string mailto;
  string jumpto;

  if (query) {
    ifstream in("wwwmail.cfg");
    string line;
    
    while (!in.eof()) {
      getline(in, line);
      if (!in.good() || in.eof()) break;

      string alias = strParse(line, ";");
      if (alias == query) {
        mailto = strParse(line, ";");
        jumpto = strParse(line, ";");
        break;
      }
    }
  }

  if (!query || mailto.empty() || jumpto.empty()) {
    cout << "Content-Type: text/plain\n"
         << "\n"
         << "Internal error!\n";
    exit(666);
  }

  // build the mail header
  char* formpage = getenv("HTTP_REFERER");
  
  ostringstream mail;
  mail << "To: " << mailto << '\n'
       << "From: " << POSTMASTER << '\n'
       << "Subject: Form submit from '"
       << (formpage ? formpage : "Unknown URL") << "'\n"
       << "\n";

  // send identification of the remote user
  echoEnv(mail, "HTTP_USER_AGENT");
  echoEnv(mail, "REMOTE_ADDR");
  echoEnv(mail, "REMOTE_IDENT");
  echoEnv(mail, "REMOTE_HOST");
  echoEnv(mail, "REMOTE_USER");

  // send form data
  int ch;
  while ((ch = cin.get(), !cin.eof())) {
    switch (ch) {
      default:  mail << char(ch); break;
      case '+': mail << ' ';      break;
      case '&': mail << '\n';     break;
      case '%': {
        char buf[3];
        buf[0] = cin.get();
        buf[1] = cin.get();
        buf[2] = '\0';
        mail << char(strtol(buf, 0, 16));
        break;
      } // case
    } // switch
  } // while
  mail << '\n';

  // send the mail
  FILE* sendmail = popen("/usr/lib/sendmail -i -t", "w");
  if (sendmail) {
    fputs(mail.str().c_str(), sendmail);
    pclose(sendmail);
  }

  // redir to follow-up page
  cout << "Location: " << jumpto << "\n\n";

  return 0;
}
wwwmail.cfg
jh;Juergen Hermann <jh@schiele-ct.de>;http://megaton.cscene.org/~snibril/
xgc;Jon Armstrong <armstron@eznet.net>;http://megaton.cscene.org/c/thankyou.html
A sample mail
Date: Thu, 25 Mar 1999 18:31:08 -0800
Message-Id: <199903260231.SAA19302@cx505299-a.fed1.sdca.home.com>
To: Juergen Hermann <jh@schiele-ct.de>
From: David Stiles <pingme@home.com>
Subject: Form submit from 'http://megaton.cscene.org/~snibril/'
X-UIDL: e7cc1b08bd03fcdfc5273bf7d79d9b44

HTTP_USER_AGENT=Mozilla/4.03 [en] (WinNT; I ;Nav)
REMOTE_ADDR=193.141.27.190
REMOTE_HOST=193.141.27.190
topic=test
memo=abc
def
button= Send it!

References

Gundavaram, Shishir, «CGI Programming on the World Wide Web».
O'Reilly, 1996, 433 p., covers CGI basics as well as hot topics like data base interfacing, details of the HTTP protocol and their use, forms, server side includes, hyper-media, client-pull and server-push (examples in Perl), ISBN 1-56592-168-2.

Hermann, Jürgen, «Sending image files via CGI».
C Scene Issue #06, 1998.

York, Brent, «CGI in C a starters tutorial».
C Scene Issue #02, 1997.


This page is Copyright © 1999 by Jürgen Hermann and Copyright © 1999 by C Scene. All Rights Reserved.