// PourCGI.cpp : Contient deux classes permettant de faire des programmes CGI simples // sous PWS ou IIS (peut-être que ça fonctionnerait ailleurs aussi...). // // - ClInfoCGI permet principalement d'accéder aux paramètres; // - ClCopieHTML permet de recopier des fichiers HTML ayant des zones à compléter. // // Voir la documentation pour plus de détails. // // par Michel Michaud, nov.2001, janvier 2002 (quelques ajustements...), // 27 janvier (ajout des fonctions de validation de paramètres) // 12 février 2002 (décomposition en .cpp et .h) // 14 février 2002 (correction pour permettre plusieurs segments par ligne) // 25 novembre 2002 (nouvelle version automne 2002, voir .h) // 18 février 2003 (mis des size_type au lieu des int pour les indices sur // string, autres ajustements du même genre...) #pragma warning(disable : 4786) // Doit être en tout premier pour éviter les warnings #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) // #include // Rallonge beaucoup le temps de compilation, donc : extern "C" __declspec(dllimport) void __stdcall Sleep(unsigned long dwMilliseconds); #endif #include "PourCGI.h" using namespace std; namespace { /***** * HexAChar : renvoie le caractère spécifié par son code hexadécimal en deux chiffres * fournis par les deux char reçus en paramètre. */ char HexAChar(char p_fort, char p_faible) { static const string CAR_HEX= "0123456789ABCDEF"; return char(CAR_HEX.find(p_fort)*16 + CAR_HEX.find(p_faible)); } /***** * CharAHex : renvoie les deux chiffres hexadécimaux (en string) du code du caractère * reçu en paramètre. */ string CharAHexString(unsigned char p_c) { static const string CAR_HEX= "0123456789ABCDEF"; return (string() + CAR_HEX[(p_c&0xF0)>>4]) + CAR_HEX[p_c&0x0F]; } /***** * GetEnv : une version C++ de getenv, qui renvoie une string plutôt qu'un char*. */ string GetEnv(const string& p_nom) { char *pValeur= getenv(p_nom.c_str()); return (pValeur == 0) ? "" : pValeur; } /***** * EstUn : indique si on peut convertir une chaîne dans le type désiré, sans erreur et * sans laisser de caractères non utilisés. */ template bool EstUn(const string& p_texte, T p_v) // Param. bidon nécessaire (bug de VC) { istringstream is(p_texte); is >> p_v; // On aurait pu utiliser une variable locale if (is.fail()) return false; is >> ws; // Ne devrait pas y en avoir, mais on les accepterait si c'est le cas return is.eof(); // Devrait atteindre eof s'il n'y a rien... } } // Fin du namespace anonyme /***** * DemarrerReponseHTTP : envoie le préfixe nécessaire pour une réponse HTML à une * requête CGI. */ void DemarrerReponseHTTP(/*const string& p_cookie*/) { static bool dejaFait= false; if (dejaFait) return; cout << "Content-Type: text/html\n"; // if (p_cookie != "") cout << "Set-cookie: " << p_cookie << '\n'; cout << endl; // Flush utile si ClInfoCGI lève une exception non captée dejaFait= true; } /***** * ClCopieHTML::EncodeHTML : renvoie une version HTML d'un texte. Tout code qui serait * interprété comme du HTML est remplacé par un équivalent. */ string ClCopieHTML::EncodeHTML(string p_texte) { string::size_type pos= 0; // Les code « &xyz; » seront remplacés par « &xyz; » for(;;) { pos= p_texte.find('&', pos); // Les code &xxx; seront remplacés par /***/ if (pos == string::npos) break; /***/ p_texte.replace(pos, 1, "&"); pos+= 5; // Pour sauter le stock qu'on vient de mettre ! } pos= 0; // Les code « Erreur d'accès au fichier " << p_nomFichier << ", contacter les responsables."; } /***** * ClCopieHTML::Destructeur */ ClCopieHTML::~ClCopieHTML() { delete m_pImpl; } /***** * ClCopieHTML::Transcrire : transcrit le fichier jusqu'à un segment à compléter ou * jusqu'à la fin de fichier. Renvoie vrai s'il y a un segment à compléter. La * fonction suppose un seul segment par ligne... à revoir ? */ bool ClCopieHTML::Transcrire() { return m_pImpl->Transcrire(); } bool ClCopieHTML_Impl::Transcrire() { string ligne= m_resteDeLigne; m_resteDeLigne= ""; m_nomSegment= ""; // Sentinelle, s'il n'y a pas de nom, il n'y a pas de segment... do { // Les segments à compléter sont notés entre [[[ et ]]], on en cherche... const string::size_type posDelimiteurDebut= ligne.find("[[["); const string::size_type posDelimiteurFin= ligne.find("]]]"); if (posDelimiteurDebut != string::npos && posDelimiteurFin != string::npos && posDelimiteurDebut < posDelimiteurFin) { cout << ligne.substr(0, posDelimiteurDebut) << flush; const string::size_type posNomSegment= posDelimiteurDebut+3; m_nomSegment= ligne.substr(posNomSegment, posDelimiteurFin-posNomSegment); m_resteDeLigne= ligne.substr(posDelimiteurFin+3); } else cout << ligne << endl; // Le flush du endl peut aider si erreur... } while (m_nomSegment.empty() && getline(m_ficHTML, ligne)); return ! m_nomSegment.empty(); } /***** * ClCopieHTML::NomSegment : renvoie le nom du segment à compléter qui a été trouvé * par Transcrire. */ string ClCopieHTML::NomSegment() const { return m_pImpl->NomSegment(); } /***** * ClInfoCGI_Impl : classe pour l'implantation opaque de la classe ClInfoCGI *****/ class ClInfoCGI_Impl { friend class ClInfoCGI; private : explicit ClInfoCGI_Impl(const string& p_nomFicSemaphore); ~ClInfoCGI_Impl(); void Annuler() { m_annuler= true; } bool EnCGI() const { return m_enCGI; } int NbParametres() const { return static_cast(m_items.size()); } void AfficherListeDesValeurs() const; string Requete() const; string Erreur() const; string ValeurStringDe(const string& p_nomParam) const; bool EstUnChar(const string& p_nomParam) const; char ValeurCharDe(const string& p_nomParam) const; bool EstUnInt(const string& p_nomParam) const; int ValeurIntDe(const string& p_nomParam) const; bool EstUnLong(const string& p_nomParam) const; long ValeurLongDe(const string& p_nomParam) const; bool EstUnDouble(const string& p_nomParam) const; double ValeurDoubleDe(const string& p_nomParam) const; string Nom(int p_noParam) const; bool m_enCGI; map m_items; // Paires « nom, valeur » ofstream m_ficSemaphore; bool m_annuler; }; ClInfoCGI_Impl* ClInfoCGI::m_pImpl= 0; /***** * ClInfoCGI::Constructeurs */ ClInfoCGI::ClInfoCGI(const string& p_nomFicSemaphore) { assert(m_pImpl == 0); // On ne devrait déclarer qu'une seule variable de type ClInfoCGI if (m_pImpl == 0) m_pImpl= new ClInfoCGI_Impl(p_nomFicSemaphore); } ClInfoCGI_Impl::ClInfoCGI_Impl(const string& p_nomFicSemaphore) { m_annuler= p_nomFicSemaphore.empty(); // On ne veut pas de message d'erreur s'il // n'y a pas de fichier sémaphore static const string FORM_DATA= "multipart/form-data; boundary="; static const string BLABLA= "Content-Disposition: form-data; name="; static const string URLENCODED= "application/x-www-form-urlencoded"; const string requestMethod= GetEnv("REQUEST_METHOD"); string contentType; string texte; if (requestMethod == "GET") { m_enCGI= true; contentType= URLENCODED; texte= GetEnv("QUERY_STRING"); } else if (requestMethod == "POST") { m_enCGI= true; contentType= GetEnv("CONTENT_TYPE"); for (int lg= atoi(GetEnv("CONTENT_LENGTH").c_str()); lg != 0; --lg) { char c; cin.get(c); texte+= c; if (c=='\n') --lg; // cin n'est pas en mode ios_base::binary... } } else { m_items["Erreur"]= "Appel du programme sans CGI."; m_enCGI= false; return; } DemarrerReponseHTTP(); if (contentType == URLENCODED) { string nom; string valeur; bool enNom= true; for (string::size_type i= 0; i <= texte.length(); ++i) // Le <= permet de traiter le dernier { if (i==texte.length() || texte[i] == '&') { if (!nom.empty() || !valeur.empty()) // On ne veut pas les ...&=&... m_items[nom]= valeur; nom= valeur= ""; enNom= true; } else if (texte[i] == '=') enNom= !enNom; else { char c; switch (texte[i]) { case '+' : c= ' '; break; case '%' : c= HexAChar(texte[i+1], texte[i+2]); i+=2; break; default : c= texte[i]; break; } if (enNom) nom+= c; else valeur+= c; } } } else if (contentType.find(FORM_DATA) == 0) { istringstream iss(texte); string ligneBoundary; getline(iss, ligneBoundary); // La première ligne est une ligne « boundary »... string ligne; while (getline(iss, ligne)) { // Le nom string nom= ligne.substr(BLABLA.size()+1, ligne.size()-2-BLABLA.size()); // Le \n superflu iss.get(); // Le début de valeur string valeur; getline(iss, valeur); for(;;) { getline(iss, ligne); /***/ if (iss.fail() || ligne.find(ligneBoundary) == 0) break; /***/ valeur+= '\n' + ligne; } m_items[nom]= valeur; } } else m_items["Erreur"]= "enctype inconnu (attention, text/plain non supporté)."; if (!p_nomFicSemaphore.empty()) { srand(static_cast(time(0))); // Essayer d'obtenir le « sémaphore », réessayer... et si ça ne fonctionne pas, quitter par une exception... for (int i= 0; i < 100; ++i) { m_ficSemaphore.open(p_nomFicSemaphore.c_str()); if (m_ficSemaphore.is_open()) return; // Introduire un délai aléatoire... #if defined(_MSC_VER) Sleep(100+rand()%200); // 100 fois donnera entre 10 et 30 secondes #else for (int j= rand()%5000; j > 0; --j) cout << "" << flush; // Pas fort... #endif } m_items["Erreur"]= "Système occupé, opération annulée pour prévenir les accès concurrents."; throw ClInfoCGI::ClSemaphoreNonObtenu(); } } /***** * ClInfoCGI::Destructeurs */ ClInfoCGI::~ClInfoCGI() { delete m_pImpl; } ClInfoCGI_Impl::~ClInfoCGI_Impl() { if ( ! m_ficSemaphore.is_open() && !m_annuler) { cout << "" "" "" "Le système est occupé, réessayer plus tard" "

Le système est occupé, réessayer plus tard.

" ""; } } /***** * ClInfoCGI::Annuler, ClInfoCGI::EnCGI, ClInfoCGI::NbParametres */ void ClInfoCGI::Annuler() { m_pImpl->Annuler(); } bool ClInfoCGI::EnCGI() const { return m_pImpl->EnCGI(); } int ClInfoCGI::NbParametres() const { return m_pImpl->NbParametres(); } /***** * ClInfoCGI::Nom : renvoie le nom d'un paramètre par son numéro (par rapport à l'ordre * alphabétique) */ string ClInfoCGI::Nom(int p_noParam) const { return m_pImpl->Nom(p_noParam); } string ClInfoCGI_Impl::Nom(int p_noParam) const { if (p_noParam < 0) return ""; map::const_iterator it= m_items.begin(); while (it != m_items.end() && p_noParam-- > 0) ++it; return (it == m_items.end()) ? "" : it->first; } /***** * ClInfoCGI::AfficherListeDesValeurs : affiche tous les paramètres. Surtout utile au * débogage. */ void ClInfoCGI::AfficherListeDesValeurs() const { m_pImpl->AfficherListeDesValeurs(); } void ClInfoCGI_Impl::AfficherListeDesValeurs() const { cout << ""; for (map::const_iterator it=m_items.begin(); it != m_items.end(); ++it) { cout << '"' << ClCopieHTML::EncodeHTML(it->first) << "\" = \"" << ClCopieHTML::EncodeHTML(it->second) << "\"
"; } cout << "
"; } /***** * ClInfoCGI::Requete : renvoie la valeur associée au paramètre "Requête" (ou "Requete") */ string ClInfoCGI::Requete() const { return m_pImpl->Requete(); } string ClInfoCGI_Impl::Requete() const { map::const_iterator it= m_items.find("Requete"); if (it != m_items.end()) return it->second; it= m_items.find("Requête"); // On essaie aussi avec accent return (it==m_items.end()) ? "" : it->second; } /***** * ClInfoCGI::Erreur : renvoie la valeur du pseudo paramètre "Erreur" */ string ClInfoCGI::Erreur() const { return m_pImpl->Erreur(); } string ClInfoCGI_Impl::Erreur() const { map::const_iterator it= m_items.find("Erreur"); return (it==m_items.end()) ? "" : it->second; } /***** * ClInfoCGI::EstUnXxx et ClInfoCGI::ValeurXxxDe * * Les fonctions EstunXxx indiquent si la valeur d'un paramètre peut être convertie * dans le type désiré, sans erreur et sans laisser de caractères non utilisés. * * Les fonctions ValeurXxxDe renvoient les valeurs converties. */ // String string ClInfoCGI::ValeurStringDe(const string& p_nomParam) const { return m_pImpl->ValeurStringDe(p_nomParam); } string ClInfoCGI_Impl::ValeurStringDe(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return (it==m_items.end()) ? "" : it->second; } // Char bool ClInfoCGI::EstUnChar(const string& p_nomParam) const { return m_pImpl->EstUnChar(p_nomParam); } bool ClInfoCGI_Impl::EstUnChar(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return it != m_items.end() && it->second.size() == 1; } char ClInfoCGI::ValeurCharDe(const string& p_nomParam) const { return m_pImpl->ValeurCharDe(p_nomParam); } char ClInfoCGI_Impl::ValeurCharDe(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return (it==m_items.end()) ? '\0' : it->second[0]; } // Int bool ClInfoCGI::EstUnInt(const string& p_nomParam) const { return m_pImpl->EstUnInt(p_nomParam); } bool ClInfoCGI_Impl::EstUnInt(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return it != m_items.end() && EstUn(it->second, int()); } int ClInfoCGI::ValeurIntDe(const string& p_nomParam) const { return m_pImpl->ValeurIntDe(p_nomParam); } int ClInfoCGI_Impl::ValeurIntDe(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return (it==m_items.end()) ? 0 : atoi(it->second.c_str()); } // Long bool ClInfoCGI::EstUnLong(const string& p_nomParam) const { return m_pImpl->EstUnLong(p_nomParam); } bool ClInfoCGI_Impl::EstUnLong(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return it != m_items.end() && EstUn(it->second, long()); } long ClInfoCGI::ValeurLongDe(const string& p_nomParam) const { return m_pImpl->ValeurLongDe(p_nomParam); } long ClInfoCGI_Impl::ValeurLongDe(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return (it==m_items.end()) ? 0 : atol(it->second.c_str()); } // Double bool ClInfoCGI::EstUnDouble(const string& p_nomParam) const { return m_pImpl->EstUnDouble(p_nomParam); } bool ClInfoCGI_Impl::EstUnDouble(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return it != m_items.end() && EstUn(it->second, double()); } double ClInfoCGI::ValeurDoubleDe(const string& p_nomParam) const { return m_pImpl->ValeurDoubleDe(p_nomParam); } double ClInfoCGI_Impl::ValeurDoubleDe(const string& p_nomParam) const { map::const_iterator it= m_items.find(p_nomParam); return (it==m_items.end()) ? 0.0 : atof(it->second.c_str()); }