OpenVMS Source Code Demos
HTTP_FILE_UPLOAD_C
//================================================================================================
// title : HTTP_FILE_UPLOAD_C_xx.c
// platform: OpenVMS-8.4 (Alpha or Itanium)
// notes : this program is meant as a drop-in diagnostic to test problems encountered with BASIC
// function "ASSET_HTTP_FILE_UPLOAD_22.fun" during the port from Alpha to Itanium.
// created : 2015-10-07
// history :
// ver who when what
// --- --- ------ --------------------------------------------------------------------------------
// 21 ... ...... 1. original BASIC function to receive uploaded files
// 22 NSR 151007 1. original C program to debug a problem with CSWS-2.2-1 (Apache for OpenVMS)
// during Itanium conversion; problem with the BASIC RTL or something else?
// 23 NSR 151008 1. ripped out superfluous routines (not germane to this problem)
// 24 NSR 151009 1. changed this code from a function to a stand-alone program
// 2. passed this code to HP/HPE with "my request for support"
// 25 NSR 151016 1. found some time to continue developing this program
// NSR 151019 1. found some time to add a little hex dump
// 26 NSR 151020 1. found some time to add data parsing
// NSR 151021 1. found some time to add file writing
// 27 NSR 151021 1. started work on strnstr for obvious reasons
// NSR 151022 1. wasted time locating a childish cut-n-paste bug
// 28 NSR 151023 1. ripped out some debug crud
// 2. reduced programmed delays
// 3. now do a better job of prepping the file name
// 4. note: this program is still only for demonstration use
// NSR 151127 5. HP/HPE reported that they have a patch for me to test
// 29 NSR 151208 1. started changes required for production use as a function
// 151210 1. found some time to continue this saga
// 30 NSR 151211 1. started work on a potential end-of-data problem bf_30.1
// (this was something that popped into my head while on vacation last week)
// 31 NSR 151214 0. renamed from "c_http_file_upload_30.c" to "http_file_upload_c_31.c"
// 1. a few tweaks as I test this for production use as a function
// 151215 2. rewrote the boundary skew logic (previous code broke with jquery-based AJAX)
//================================================================================================
#define PROGRAM_VER "http_file_upload_c_31.1" // only used in Apache logging
#define FUNCTION 1 // program=0, function=1
#define __NEW_STARLET 1 //
#define NSR_DISPLAY_HEX_DATA 0 // 0=off, 1=on
#define SMALL_STRING 255 //
#define LARGE_STRING 32700 //
#include <stdio.h> //
#include <stdlib.h> //
#include <string.h> //
#include <ctype.h> //
#include <errno.h>
//
// OpenVMS specific stuff
//
#include <descrip.h> // for vms string descriptors in C
#include <str$routines.h> // for vms string descriptors in VMS
#include <ssdef.h> //
#include <lib$routines.h> //
#include <libwaitdef.h> //
//
// VMSIFY
// a macro for use in the VMS world (VMS strings employ this structure)
// notes: this macro can be used to pass VMS strings down to lower modules
// the $DESCRIPTOR macro does something similar employing sizeof-1
// this macro combines two operations
//
#define VMSIFY(a,b) { \
a.dsc$b_dtype = DSC$K_DTYPE_T; \
a.dsc$b_class = DSC$K_CLASS_S; \
a.dsc$w_length = strlen(b); \
a.dsc$a_pointer = (char *) malloc(strlen(b)); \
strncpy(a.dsc$a_pointer,b,a.dsc$w_length); \
}
//------------------------------------------------------------------------------
// delay()
// a quick hack to introduce a programmed delay
//------------------------------------------------------------------------------
#if __G_FLOAT != 0
# define FLOAT_TYPE LIB$K_VAX_F
#elif __D_FLOAT != 0
# define FLOAT_TYPE LIB$K_VAX_D
#elif __IEEE_FLOAT != 0
# define FLOAT_TYPE LIB$K_IEEE_S
#else
# error "Try specifying a floating point qualifier on the compile"
#endif
//
void delay(float delay_time){
long rc;
unsigned long float_type = FLOAT_TYPE;
rc = lib$wait(&delay_time,0,&float_type);
}
void dualout_sl(char*,long);
void dualout_s(char*);
#if (NSR_DISPLAY_HEX_DATA==1)
//------------------------------------------------------------------------------
// hexdisplay()
// just a quick hack so I can debug this interface
//------------------------------------------------------------------------------
void hexdisplay(char *p, long count){
unsigned long z;
printf("-i-hex data dump:\n");
for (long j=0; j<count; j=j+16){
printf("%4x: ",j);
for (long i=0; i<16; i++){
if ((i+j)<count){
z = p[i+j];
z = abs(z);
printf("%2x ",z);
}else{
printf(" ");
}
}
for (long i=0; i<16; i++){
if ((i+j)<count){
z = p[i+j];
z = abs(z);
if (z<32)
printf(". ");
if ((z>=32)&&(z<=126))
printf("%c ",z);
if (z>126)
printf(". ");
}else{
printf(" ");
}
}
printf("\n");
}
}
#endif
//------------------------------------------------------------------------------
// strnstr()
// notes: 1. similar to strstr but scans n bytes in s while ignoring NULL
// 2. this version doesn't stop on a NULL
//------------------------------------------------------------------------------
char * strnstr(const char *s, const char *find, size_t slen) {
char c, sc;
size_t len;
if ((c = *find++) != '\0') {
len = strlen(find);
do {
do {
// if ((sc = *s++) == '\0' || slen-- < 1 ) xx
// return (NULL); xx
if ( slen-- < 1 )
return (NULL);
sc = *s++;
} while (sc != c);
if (len > slen)
return (NULL);
} while (strncmp(s, find, len) != 0);
s--;
}
return ((char *)s);
}
//------------------------------------------------------------------------------
// my_fread()
// note: conceived at the gym, this hack is more efficient than fread()
//------------------------------------------------------------------------------
long my_fread(char* buffer, long unit, long limit, FILE *stream){
long yada;
long i;
for (i=0; i<limit;i++){
yada = getc(stream);
if (yada==EOF){
break;
}
buffer[i] = yada;
}
return(i);
}
long gDebug; // needs to be global
//------------------------------------------------------------------------------
// dualout
//------------------------------------------------------------------------------
void dualout_s(char *one){ !
fprintf(stderr,one); // goes to Apache logs
if (gDebug>0)
fprintf(stdout,one); // goes to user
}
void dualout_ss(char *one, char *two){
fprintf(stderr,one, two);
if (gDebug>0)
fprintf(stdout,one,two);
}
void dualout_sl(char *one, long two){
fprintf(stderr,one, two);
if (gDebug>0)
fprintf(stdout,one,two);
}
void dualout_sls(char *one, long two, char *three){
fprintf(stderr,one, two, three);
if (gDebug>0)
fprintf(stdout,one,two,three);
}
//==============================================================================
// main()
//==============================================================================
#if (FUNCTION==0)
//---------------------------------------------------------------
// DEVELOPMENT PROGRAM
// a stand-alone program (for testing and maintenance)
//---------------------------------------------------------------
long main(long argc, char *argv[]) {
#else
//---------------------------------------------------------------
// PRODUCTON FUNCTION
// a long function called from BASIC
//
// BASIC declaration:
// external long function HTTP_FILE_UPLOAD_C( &
// string directory$, &
// string filename$, &
// long debug)
// notes: p1_directory might be specified or blank
// p3_debug might be 0-3
// p2_filename write back the actual filename
//---------------------------------------------------------------
long http_file_upload_c(
struct dsc$descriptor_d *p1_directory,
struct dsc$descriptor_d *p2_filename,
long *p3_debug) {
#endif
long ContentLength; // caveat: 2G limit for now
long i; //
long x; //
long temp; //
char *sod; // start-of-data
char *eod; // end-of-data
char *p; //
char *pRequestMethod; //
char *pContentType1; //
char *pFileName; //
char *pDirectory; //
FILE *fd_in; // file descriptor
FILE *fd_out; // file descriptor
long rc = 0; //
long bytes_r = 0; //
long total_bytes_r = 0; // caveat: 2G limit for now
long remaining = 0; //
long bytes_w = 0; //
long total_bytes_w = 0; //
long max_gulp = 0; //
long zero_counter = 0; //
long read_count = 0; //
long boundary_skew = 0; // for Chrome and Firefox
long text_file = 0; // init to 'not text'
char buffer0[LARGE_STRING+1]; //
char filename[SMALL_STRING+1]; //
char directory[SMALL_STRING+1]; //
char boundary[SMALL_STRING+1]; //
char scratch1[SMALL_STRING+1]; //
char contentType2[SMALL_STRING+1]; //
char fqfs[SMALL_STRING+SMALL_STRING]; // fully qualified file specification
struct dsc$descriptor_s vms_misc; //
//--------------------------------------------------------------------------
// real code starts here :-)
//--------------------------------------------------------------------------
#if (FUNCTION==0) // this is a program
gDebug = 1; //
#else // this is a function
memcpy(&gDebug,p3_debug,sizeof(gDebug)); //
#endif
if (gDebug>0){ //
printf("Status: 200\n"); // signal okay while we debug
printf("Cache-Control: no-store\n"); //
printf("Content-type: text/html\n\n"); // end of HTTP header
printf("<!DOCTYPE html>\n"); // start of payload
printf("<html>\n<head></head>\n<body><pre>"); //
}
#if (FUNCTION==0)
printf("-i-program : %s\n",argv[0]); // display program name to user
pDirectory = NULL; //
#else
dualout_ss("-i-program %s\n", PROGRAM_VER); // display program name to Apache logs
if (p1_directory->dsc$w_length>0) { // if a directory was provided
pDirectory=malloc(p1_directory->dsc$w_length+5); //
if (pDirectory==NULL) { //
dualout_s("-e-insufficient memory\n"); //
goto error_exit; // I'll go to c-programmer's hell for this
} //
strncpy(pDirectory, (p1_directory->dsc$a_pointer), (p1_directory->dsc$w_length));
pDirectory[p1_directory->dsc$w_length] = '\0'; //
}
#endif
if (pDirectory==NULL) { //
pDirectory = getenv("CGI$UPLOAD_DIRECTORY"); // optional (DCL symbol or OpenVMS logical symbol)
} //
if (pDirectory==NULL) { //
sprintf(directory,"csmis$tmp:"); // default upload location
}else{ //
sprintf(directory,pDirectory); //
}
#if (FUNCTION==0) //
printf("-i-directory : %s\n",directory); //
#else //
dualout_ss("-i-directory %s\n", directory); //
#endif
//
pRequestMethod = getenv("REQUEST_METHOD"); // set by Apache mod_cgi
if (pRequestMethod==NULL) { //
dualout_s("-e-REQUEST_METHOD: NULL\n"); //
goto error_exit; //
}else{ //
if (gDebug>0) //
printf("-i-REQUEST_METHOD : %s\n",pRequestMethod); //
} //
if (strcasecmp(pRequestMethod,"POST")!=0) { //
dualout_ss("-e-unsupported request method: %s\n",pRequestMethod); //
goto error_exit; //
}
pContentType1 = getenv("CONTENT_TYPE"); // set by Apache mod_cgi
if (pContentType1==NULL) { //
dualout_s("-e-CONTENT_TYPE: NULL\n"); //
goto error_exit; //
}
//
// we only support multipart/form-data but here is what we usually see:
// multipart/form-data; boundary=---------------------------24743530619118
//
if (strcasecmp(pContentType1,"application/x-www-form-urlencoded")==0) { //
dualout_ss("-e-unsupported content type: %s\n",pContentType1); //
goto error_exit; //
}
if (gDebug>0) //
printf("-i-CONTENT_TYPE : %s\n",pContentType1); //
//
p = strstr(pContentType1,"boundary="); //
if (p==NULL){ //
dualout_s("-e-BOUNDARY: NULL\n"); //
goto error_exit; //
}else{ //
sprintf(boundary,p+9); // skip over the text (scoop up the rest)
if (gDebug>0) //
printf("-i-defined boundary : %s\n", boundary); //
dualout_ss("-i-defined boundary : %s\n", boundary); //
} //
//
p = getenv("CONTENT_LENGTH"); // set by Apache mod_cgi
if (p!=NULL) { //
ContentLength = atoi(p); // caveat: 2G limit
} else { //
ContentLength = 0; //
}
if (gDebug>0) //
printf("-i-CONTENT_LENGTH : %ld\n",ContentLength); //
fd_in = fopen("APACHE$INPUT","rb"); //
delay(0.500); // delay 500 mS b4 first read
//--------------------------------------------------------------------------
// time to get down to work
//--------------------------------------------------------------------------
read_loop:
//
// note: as this program is currently written, some transactions will split the final boundary across
// the second last and last block. Setting max_gulp to something a little smaller will prevent this.
//
remaining = ContentLength - total_bytes_r; // bf_30.1
if (remaining <= LARGE_STRING) // enough buffer space to read this? bf_30.1
max_gulp = LARGE_STRING; // yes (use max buf)
else{ //
if ((remaining - LARGE_STRING) >= 100) // will the next read leave at least 100?
max_gulp = LARGE_STRING; // yes
else // no
max_gulp = LARGE_STRING - 100; // so do a partial read
}
//
bytes_r = 0; // init
read_count++; // update
if (fd_in) {
// bytes_r = fread(buffer0,1,LARGE_STRING,fd_in); xx
// bytes_r = my_fread(buffer0,1,LARGE_STRING,fd_in); xx
bytes_r = my_fread(buffer0,1,max_gulp ,fd_in); // bf_30.1
}
// buffer0[bytes_r] = '\0'; // for printf in older debug
total_bytes_r = total_bytes_r + bytes_r; //
if (gDebug>0) { //
printf("-i-read_count : %ld\n",read_count); //
printf("-i-bytes received: %ld\n",bytes_r); //
printf("-i-total bytes received: %ld\n",total_bytes_r); //
}
//
// we need to do a little prep after the first read
//
if (read_count==1){ // if first read
//
// test/repair the boundary
//
p = strstr(buffer0,boundary); // locate the primary boundary in the data
if (p==NULL){ // this should never happen
dualout_s("-e-could not find initial boundary\n"); //
goto error_exit;
}
boundary_skew = p-buffer0; // did it start at zero? (maybe)
if (gDebug>0) //
printf("-i-boundary skew : %ld\n",boundary_skew); //
//
// locate filename IE: "c:\path\yada.txt" or "c:\yada.txt"
// FF: "yada.txt"
//
p = strstr(buffer0,"filename=\""); //
if (p==NULL){ //
dualout_s("-e-could not find filename\n"); //
goto error_exit; //
}
p = p + 10; //
filename[0] = '\0'; //
for (long i=0; i<SMALL_STRING; i++){ //
if (p[i]=='\0') //
break; //
if (p[i]=='"') //
break; //
filename[i ] = toupper(p[i]); //
filename[i+1] = '\0'; //
}
//
// IE patch
//
do {
p = strstr(filename,"\\"); // any backslash here?
if (p!=NULL){ // yes
sprintf(scratch1,p+1); //
sprintf(filename,scratch1); //
}
} while (p!=NULL); //
if (strlen(filename)<3){ //
dualout_s("-e-filename too small\n"); //
goto error_exit; //
}
//
// remove spaces, plus signs, etc. (can't save to OpenVMS)
//
for (long i=0, j=0; (i<strlen(filename)); i++){ //
switch(filename[i]){ //
case ' ': // space
case '\t': // tab
case '_': // underscore
scratch1[j]='-'; // replace with dash
j++; //
scratch1[j]='\0'; //
continue; //
case '.': //
case '-': //
scratch1[j]=filename[i]; // transcribe
j++; //
scratch1[j]='\0'; //
continue; //
} //
if (isalnum(filename[i])) { //
scratch1[j]=filename[i]; // transcribe
j++; //
scratch1[j]='\0'; //
continue; //
} //
}
sprintf(filename,"%s",scratch1); //
//
// ensure only one dot (scan from right to left)
//
for (long i=strlen(filename), k=0; i!=0; i--){ //
if (filename[i]=='.'){ //
k++; // dot counter
if (k>1) { // if not the first from the end
filename[i]='-'; // replace with dash
} //
} //
} //
//
// reduce multiple dashes to one
//
for (long i=0, j=0, k=0; (i<strlen(filename)); i++){ //
if (filename[i]=='-'){ //
k++; // dash counter
if (k>1) // if more than one in a row
continue; // just ignore it
}else{ //
k=0; // reset
}
scratch1[j]=filename[i]; // transcribe
j++; //
scratch1[j]='\0'; //
}
sprintf(filename,"%s",scratch1); //
if (strlen(filename)<3){ //
dualout_s("-e-filename too small\n"); //
goto error_exit; //
}
sprintf(fqfs,"%s%s",directory,filename); //
if (gDebug>0){ //
printf("-i-filename : %s\n",filename); //
printf("-i-fqfs : %s\n",fqfs); //
}
//
// locate content-type without the trailing space
//
p = strstr(buffer0,"Content-Type:"); //
if (p==NULL){ //
dualout_s("-e-could not find Content-Type\n"); //
goto error_exit; //
}
p = p + 13; //
contentType2[0] = '\0'; // init
for (long i=0; i<SMALL_STRING; i++){ //
if (p[i]!='\x0d'){ // if not <cr>
contentType2[i ] = p[i]; // then copy data
contentType2[i+1] = '\0'; // and advance the terminator
}else{ //
break; //
} //
}
if (gDebug>0) //
printf("-i-ContentType : %s\n",contentType2); //
//
// is this a text file?
//
p = strstr(contentType2, "text/"); //
if (p!=NULL) //
text_file = 1; //
p = strstr(filename, ".XML"); //
if (p!=NULL) //
text_file = 1; //
p = strstr(filename, ".CSV"); //
if (p!=NULL) //
text_file = 1; //
p = strstr(filename, ".MHT"); //
if (p!=NULL) //
text_file = 1; //
if (gDebug>0) //
printf("-i-text file? : %d\n", text_file); //
//
sod = strstr(buffer0, boundary); //
if (sod==NULL){ //
dualout_s("-e-could not find the working boundary\n"); //
goto error_exit; //
}
sod = sod + strlen(boundary); //
sod = strstr(sod, "\x0d\x0a\x0d\x0a"); // look for end of header
if (sod==NULL){ //
dualout_s("-e-could not find start-of-data marker\n"); //
goto error_exit; //
}
sod = sod + 4; // this is the start-of-data
//
if (text_file==0){ // if not a text file
fd_out = fopen(fqfs,"wb","rat=none","rfm=fix","mrs=512"); // fixed block sizes
}else{ // else a test file
fd_out = fopen(fqfs,"wb","rat=none","rfm=stm"); // no record attributes; stream
}
if (fd_out==NULL) { //
dualout_sls("-e-error: %ld while opening file : %s\n",errno,fqfs); //
goto error_exit; //
}
}else{ // else not first read
sod = buffer0; // always zap to zero
}
//------------------------------------------------------------------------------
// process received data
// entry: buffer0 = full data buffer
// bytes_r = byte count
// *SOD = start-of-data
//------------------------------------------------------------------------------
#if (NSR_DISPLAY_HEX_DATA==1)
if (gDebug>1) // more than simple debugging
hexdisplay(buffer0,bytes_r); //
#endif
//------------------------------------------------------------------------------
if (total_bytes_r != ContentLength) { // if not complete
if (bytes_r==0) { // oops, nothing received
zero_counter++; //
if (gDebug>0) //
printf("-i-zero_counter: %ld\n",zero_counter); //
if (zero_counter < 5){ //
delay(1.0); // delay 1 S
if (gDebug>0) //
printf("-i-fetching more data\n"); //
goto read_loop; //
}else{ //
dualout_s("-e-message: DATA IS MISSING\n"); //
goto error_exit; //
}
}else{ // something received
temp = bytes_r - (sod - buffer0); //
// bytes_w = fwrite(sod,1,temp,fd_out); // why does this write wide characters?
for (bytes_w=0; bytes_w<temp; bytes_w++){ //
x = fputc(sod[bytes_w], fd_out); // much better
}
total_bytes_w = total_bytes_w + bytes_w; //
if (gDebug>0){ //
printf("-i-wrote bytes : %ld\n", bytes_w); //
printf("-i-wrote total bytes : %ld\n", total_bytes_w); //
}
delay(0.010); // delay 10 mS before next read
goto read_loop;
}
}
//
// if all the bytes have been received then look for the boundary marker
//
if (total_bytes_r == ContentLength){
eod = strnstr(sod, boundary,bytes_r);
if (eod==NULL){
dualout_s("-e-could not find ending boundary\n");
goto error_exit;
}
//
// caveat: both Chrome and Firefox report a boundary two bytes shorter than what was used so at this point we back up
// skew bytes (if I was really careful I would check to make sure they are dashes). The we need to back up to more
// bytes to skip over an end-of-line pair (if I was really careful I would check to make sure they are \x0d and \x0a)
//
temp = eod - sod - 2 - boundary_skew;
if (temp>0){
// bytes_w = fwrite(sod,0,temp,fd_out); // why does this write wide characters?
for (bytes_w=0; bytes_w<temp; bytes_w++){
x = fputc(sod[bytes_w], fd_out); // much better
}
total_bytes_w = total_bytes_w + bytes_w;
if (gDebug>0){
printf("-i-wrote bytes : %ld\n", bytes_w);
printf("-i-wrote total bytes : %ld\n", total_bytes_w);
}
}
}
//------------------------------------------------------------------------------
if (total_bytes_r == ContentLength){
if (gDebug>0){
printf("-i-message: ALL DATA WAS RECEIVED\n");
printf("-i-total bytes received: %ld\n",total_bytes_r);
printf("-i-total bytes saved : %ld\n",total_bytes_w);
}
}
rc = 1; // OpenVMS-s-
goto exit_common;
//
error_exit:
rc = 2; // OpenVMS-e-
//
exit_common:
if (fd_in) {
fclose(fd_in);
}
if (fd_out) {
fclose(fd_out);
}
if (gDebug>0){
printf("-i-program is exiting\n");
printf("</pre>\n</body>\n</html>");
}
#if (FUNCTION==0)
return(1); // OpenVMS-s-
#else
VMSIFY(vms_misc, filename); //
rc = str$copy_dx(p2_filename, &vms_misc); // tell function what the filename was
return(rc); // OpenVMS-?-
#endif
}