Encoder / décoder les URL en C ++ [fermé]


85

Est-ce que quelqu'un connaît un bon code C ++ qui fait cela?


3
Que diriez-vous d'accepter une réponse?
gsamaras

Réponses:


81

J'ai fait face à la moitié d'encodage de ce problème l'autre jour. Insatisfait des options disponibles, et après avoir jeté un coup d'œil à cet exemple de code C , j'ai décidé de lancer ma propre fonction de codage d'url C ++:

#include <cctype>
#include <iomanip>
#include <sstream>
#include <string>

using namespace std;

string url_encode(const string &value) {
    ostringstream escaped;
    escaped.fill('0');
    escaped << hex;

    for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        string::value_type c = (*i);

        // Keep alphanumeric and other accepted characters intact
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << uppercase;
        escaped << '%' << setw(2) << int((unsigned char) c);
        escaped << nouppercase;
    }

    return escaped.str();
}

La mise en œuvre de la fonction de décodage est laissée comme un exercice au lecteur. : P


1
Je pense qu'il est plus générique (plus généralement correct) de remplacer «» par «% 20». J'ai mis à jour le code en conséquence; n'hésitez pas à revenir en arrière si vous n'êtes pas d'accord.
Josh Kelley

1
Non, je suis d'accord. setw(0)J'ai également profité de l'occasion pour supprimer cet appel inutile (à l'époque, je pensais que la largeur minimale resterait définie jusqu'à ce que je la modifie à nouveau, mais en fait, elle est réinitialisée après la prochaine entrée).
xperroni

1
J'ai dû ajouter std :: uppercase à la ligne "escape << '%' << std :: uppercase << std :: setw (2) << int ((unsigned char) c);" Au cas où d'autres personnes se demanderaient pourquoi cela renvoie par exemple% 3a au lieu de% 3A
gumlym

2
Cela semble incorrect car les chaînes UTF-8 ne sont pas prises en charge ( w3schools.com/tags/ref_urlencode.asp ). Cela semble fonctionner uniquement pour Windows-1252
Skywalker13

1
Le problème était juste isalnum(c), il doit être changé enisalnum((unsigned char) c)
Skywalker13

74

Répondre à ma propre question ...

libcurl a curl_easy_escape pour l'encodage.

Pour le décodage, curl_easy_unescape


4
Vous devez accepter cette réponse pour qu'elle soit affichée en haut (et que les gens puissent la trouver plus facilement).
Mouagip

vous devez utiliser curl pour que cela fonctionne et libérer la mémoire
xinthose

Question connexe: pourquoi l'unescape de curl ne gère-t-il pas le changement de «+» en espace? N'est-ce pas cette procédure standard lors du décodage d'URL?
Stéphane

12
string urlDecode(string &SRC) {
    string ret;
    char ch;
    int i, ii;
    for (i=0; i<SRC.length(); i++) {
        if (int(SRC[i])==37) {
            sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
            ch=static_cast<char>(ii);
            ret+=ch;
            i=i+2;
        } else {
            ret+=SRC[i];
        }
    }
    return (ret);
}

pas le meilleur, mais ça marche bien ;-)


5
Bien sûr, vous devriez utiliser à la '%'place de 37.
John Zwinck

4
Cela ne convertit pas '+' en espace
xryl669

11

cpp-netlib a des fonctions

namespace boost {
  namespace network {
    namespace uri {    
      inline std::string decoded(const std::string &input);
      inline std::string encoded(const std::string &input);
    }
  }
}

ils permettent d'encoder et de décoder des chaînes d'URL très facilement.


2
Merci omg. la documentation sur cpp-netlib est rare. Avez-vous des liens vers de bonnes feuilles de triche?
user249806

8

Normalement, l'ajout de '%' à la valeur int d'un char ne fonctionnera pas lors de l'encodage, la valeur est censée correspondre à l'équivalent hexadécimal. par exemple '/' est '% 2F' et non '% 47'.

Je pense que ce sont les meilleures solutions concises pour le codage et le décodage d'url (pas beaucoup de dépendances d'en-tête).

string urlEncode(string str){
    string new_str = "";
    char c;
    int ic;
    const char* chars = str.c_str();
    char bufHex[10];
    int len = strlen(chars);

    for(int i=0;i<len;i++){
        c = chars[i];
        ic = c;
        // uncomment this if you want to encode spaces with +
        /*if (c==' ') new_str += '+';   
        else */if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') new_str += c;
        else {
            sprintf(bufHex,"%X",c);
            if(ic < 16) 
                new_str += "%0"; 
            else
                new_str += "%";
            new_str += bufHex;
        }
    }
    return new_str;
 }

string urlDecode(string str){
    string ret;
    char ch;
    int i, ii, len = str.length();

    for (i=0; i < len; i++){
        if(str[i] != '%'){
            if(str[i] == '+')
                ret += ' ';
            else
                ret += str[i];
        }else{
            sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii);
            ch = static_cast<char>(ii);
            ret += ch;
            i = i + 2;
        }
    }
    return ret;
}

if(ic < 16) new_str += "%0"; À quoi sert cette restauration ?? @tormuto @reliasn
KriyenKP

1
@Kriyen il est utilisé pour remplir le HEX codé avec un zéro non significatif au cas où il en résulterait une seule lettre; puisque 0 à 15 en HEX est de 0 à F.
tormuto

1
J'aime le mieux cette approche. +1 pour l'utilisation des bibliothèques standard. Bien qu'il y ait deux problèmes à résoudre. Je suis tchèque et j'ai utilisé la lettre "ý". Le résultat était "% 0FFFFFFC3% 0FFFFFFBD". Il n'est pas nécessaire d'utiliser d'abord le commutateur 16 car utf8 garantit le début de tous les octets de fin avec 10 et cela a semblé échouer mon multi-octet. Le deuxième problème est le FF car tous les ordinateurs n'ont pas la même quantité de bits par int. Le correctif consistait à sauter le commutateur 16 (non nécessaire) et à récupérer les deux derniers caractères du tampon. (J'ai utilisé stringstream car je me sens plus à l'aise avec un string buffer). A toujours donné le point. Comme le cadre aussi
Volt

@Volt seriez-vous en mesure de publier votre code mis à jour dans une nouvelle réponse? Vous mentionnez les problèmes, mais ce n'est pas assez d'informations pour une solution évidente.
gregn3

Cette réponse pose quelques problèmes, car elle utilise strlen. Premièrement, cela n'a pas de sens, car nous connaissons déjà la taille d'un objet chaîne, c'est donc une perte de temps. Bien pire cependant, une chaîne peut contenir 0 octets, ce qui serait perdu à cause du strlen. De plus, if (i <16) est inefficace, car cela peut être couvert par printf lui-même en utilisant "%%% 02X". Et enfin c devrait être un octet non signé, sinon vous obtenez l'effet que @Volt décrivait avec '0xFFF ...' en tête.
Devolus

8

[Mode Nécromancien activé]
Je suis tombé sur cette question lors de la recherche d'une solution rapide, moderne, indépendante de la plate-forme et élégante. N'a pas aimé tout ce qui précède, cpp-netlib serait le gagnant mais il a une vulnérabilité de mémoire horrible dans la fonction "décodée". J'ai donc proposé la solution spirituelle qi / karma de Boost.

namespace bsq = boost::spirit::qi;
namespace bk = boost::spirit::karma;
bsq::int_parser<unsigned char, 16, 2, 2> hex_byte;
template <typename InputIterator>
struct unescaped_string
    : bsq::grammar<InputIterator, std::string(char const *)> {
  unescaped_string() : unescaped_string::base_type(unesc_str) {
    unesc_char.add("+", ' ');

    unesc_str = *(unesc_char | "%" >> hex_byte | bsq::char_);
  }

  bsq::rule<InputIterator, std::string(char const *)> unesc_str;
  bsq::symbols<char const, char const> unesc_char;
};

template <typename OutputIterator>
struct escaped_string : bk::grammar<OutputIterator, std::string(char const *)> {
  escaped_string() : escaped_string::base_type(esc_str) {

    esc_str = *(bk::char_("a-zA-Z0-9_.~-") | "%" << bk::right_align(2,0)[bk::hex]);
  }
  bk::rule<OutputIterator, std::string(char const *)> esc_str;
};

L'utilisation de ci-dessus comme suit:

std::string unescape(const std::string &input) {
  std::string retVal;
  retVal.reserve(input.size());
  typedef std::string::const_iterator iterator_type;

  char const *start = "";
  iterator_type beg = input.begin();
  iterator_type end = input.end();
  unescaped_string<iterator_type> p;

  if (!bsq::parse(beg, end, p(start), retVal))
    retVal = input;
  return retVal;
}

std::string escape(const std::string &input) {
  typedef std::back_insert_iterator<std::string> sink_type;
  std::string retVal;
  retVal.reserve(input.size() * 3);
  sink_type sink(retVal);
  char const *start = "";

  escaped_string<sink_type> g;
  if (!bk::generate(sink, g(start), input))
    retVal = input;
  return retVal;
}

[Mode nécromancien désactivé]

EDIT01: correction du zéro padding - merci spécial à Hartmut Kaiser
EDIT02: Live on CoLiRu


Quelle est la «vulnérabilité horrible de la mémoire» cpp-netlib? Pouvez-vous fournir une brève explication ou un lien?
Craig M. Brandenburg

Il (le problème) a déjà été signalé, donc je n'ai pas signalé et je ne me souviens pas ... quelque chose comme une violation d'accès en essayant d'analyser une séquence d'échappement non valide, ou quelque chose
kreuzerkrieg


Merci de clarifier!
Craig M. Brandenburg


6

Inspiré par xperroni, j'ai écrit un décodeur. Merci pour le pointeur.

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

char from_hex(char ch) {
    return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

string url_decode(string text) {
    char h;
    ostringstream escaped;
    escaped.fill('0');

    for (auto i = text.begin(), n = text.end(); i != n; ++i) {
        string::value_type c = (*i);

        if (c == '%') {
            if (i[1] && i[2]) {
                h = from_hex(i[1]) << 4 | from_hex(i[2]);
                escaped << h;
                i += 2;
            }
        } else if (c == '+') {
            escaped << ' ';
        } else {
            escaped << c;
        }
    }

    return escaped.str();
}

int main(int argc, char** argv) {
    string msg = "J%C3%B8rn!";
    cout << msg << endl;
    string decodemsg = url_decode(msg);
    cout << decodemsg << endl;

    return 0;
}

edit: Suppression des inclusions cctype et iomainip inutiles.


1
Le bloc "if (c == '%')" a besoin de plus de vérification hors limites, i [1] et / ou i [2] peuvent être au-delà de text.end (). Je renommerais également «échappé» en «non échappé». "escaped.fill ('0');" est probablement inutile.
roalz

S'il vous plaît, regardez ma version. C'est plus optimisé. pastebin.com/g0zMLpsj
KoD

4

Ajout d'un suivi à la recommandation de Bill pour l'utilisation de libcurl: excellente suggestion, et à mettre à jour:
après 3 ans, le fonction curl_escape est obsolète, donc pour une utilisation future, il est préférable d'utiliser curl_easy_escape .


4

Je me suis retrouvé sur cette question lors de la recherche d'une api pour décoder l'URL dans une application win32 c ++. Puisque la question ne spécifie pas tout à fait la plate-forme, supposer que Windows n'est pas une mauvaise chose.

InternetCanonicalizeUrl est l'API pour les programmes Windows. Plus d'infos ici

        LPTSTR lpOutputBuffer = new TCHAR[1];
        DWORD dwSize = 1;
        BOOL fRes = ::InternetCanonicalizeUrl(strUrl, lpOutputBuffer, &dwSize, ICU_DECODE | ICU_NO_ENCODE);
        DWORD dwError = ::GetLastError();
        if (!fRes && dwError == ERROR_INSUFFICIENT_BUFFER)
        {
            delete lpOutputBuffer;
            lpOutputBuffer = new TCHAR[dwSize];
            fRes = ::InternetCanonicalizeUrl(strUrl, lpOutputBuffer, &dwSize, ICU_DECODE | ICU_NO_ENCODE);
            if (fRes)
            {
                //lpOutputBuffer has decoded url
            }
            else
            {
                //failed to decode
            }
            if (lpOutputBuffer !=NULL)
            {
                delete [] lpOutputBuffer;
                lpOutputBuffer = NULL;
            }
        }
        else
        {
            //some other error OR the input string url is just 1 char and was successfully decoded
        }

InternetCrackUrl ( ici ) semble également avoir des indicateurs pour spécifier s'il faut décoder l'url


3

Je n'ai pas trouvé de décodage / unescape URI ici qui décode également des séquences de 2 et 3 octets. En contribuant à ma propre version haute performance, qui convertit à la volée l'entrée c sting en wstring:

#include <string>

const char HEX2DEC[55] =
{
     0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
    -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,10,11,12, 13,14,15
};

#define __x2d__(s) HEX2DEC[*(s)-48]
#define __x2d2__(s) __x2d__(s) << 4 | __x2d__(s+1)

std::wstring decodeURI(const char * s) {
    unsigned char b;
    std::wstring ws;
    while (*s) {
        if (*s == '%')
            if ((b = __x2d2__(s + 1)) >= 0x80) {
                if (b >= 0xE0) { // three byte codepoint
                    ws += ((b & 0b00001111) << 12) | ((__x2d2__(s + 4) & 0b00111111) << 6) | (__x2d2__(s + 7) & 0b00111111);
                    s += 9;
                }
                else { // two byte codepoint
                    ws += (__x2d2__(s + 4) & 0b00111111) | (b & 0b00000011) << 6;
                    s += 6;
                }
            }
            else { // one byte codepoints
                ws += b;
                s += 3;
            }
        else { // no %
            ws += *s;
            s++;
        }
    }
    return ws;
}

#define __x2d2__(s) (__x2d__(s) << 4 | __x2d__(s+1))et il sera construit avec -WError.
Janek Olszak

Désolé mais "haute performance" tout en ajoutant des caractères uniques à un wstringest irréaliste. Au moins reserveassez d'espace, sinon vous aurez des réaffectations massives tout le temps
Felix Dombek


1

Cette version est en C pur et peut éventuellement normaliser le chemin de la ressource. Son utilisation avec C ++ est simple:

#include <string>
#include <iostream>

int main(int argc, char** argv)
{
    const std::string src("/some.url/foo/../bar/%2e/");
    std::cout << "src=\"" << src << "\"" << std::endl;

    // either do it the C++ conformant way:
    char* dst_buf = new char[src.size() + 1];
    urldecode(dst_buf, src.c_str(), 1);
    std::string dst1(dst_buf);
    delete[] dst_buf;
    std::cout << "dst1=\"" << dst1 << "\"" << std::endl;

    // or in-place with the &[0] trick to skip the new/delete
    std::string dst2;
    dst2.resize(src.size() + 1);
    dst2.resize(urldecode(&dst2[0], src.c_str(), 1));
    std::cout << "dst2=\"" << dst2 << "\"" << std::endl;
}

Les sorties:

src="/some.url/foo/../bar/%2e/"
dst1="/some.url/bar/"
dst2="/some.url/bar/"

Et la fonction réelle:

#include <stddef.h>
#include <ctype.h>

/**
 * decode a percent-encoded C string with optional path normalization
 *
 * The buffer pointed to by @dst must be at least strlen(@src) bytes.
 * Decoding stops at the first character from @src that decodes to null.
 * Path normalization will remove redundant slashes and slash+dot sequences,
 * as well as removing path components when slash+dot+dot is found. It will
 * keep the root slash (if one was present) and will stop normalization
 * at the first questionmark found (so query parameters won't be normalized).
 *
 * @param dst       destination buffer
 * @param src       source buffer
 * @param normalize perform path normalization if nonzero
 * @return          number of valid characters in @dst
 * @author          Johan Lindh <johan@linkdata.se>
 * @legalese        BSD licensed (http://opensource.org/licenses/BSD-2-Clause)
 */
ptrdiff_t urldecode(char* dst, const char* src, int normalize)
{
    char* org_dst = dst;
    int slash_dot_dot = 0;
    char ch, a, b;
    do {
        ch = *src++;
        if (ch == '%' && isxdigit(a = src[0]) && isxdigit(b = src[1])) {
            if (a < 'A') a -= '0';
            else if(a < 'a') a -= 'A' - 10;
            else a -= 'a' - 10;
            if (b < 'A') b -= '0';
            else if(b < 'a') b -= 'A' - 10;
            else b -= 'a' - 10;
            ch = 16 * a + b;
            src += 2;
        }
        if (normalize) {
            switch (ch) {
            case '/':
                if (slash_dot_dot < 3) {
                    /* compress consecutive slashes and remove slash-dot */
                    dst -= slash_dot_dot;
                    slash_dot_dot = 1;
                    break;
                }
                /* fall-through */
            case '?':
                /* at start of query, stop normalizing */
                if (ch == '?')
                    normalize = 0;
                /* fall-through */
            case '\0':
                if (slash_dot_dot > 1) {
                    /* remove trailing slash-dot-(dot) */
                    dst -= slash_dot_dot;
                    /* remove parent directory if it was two dots */
                    if (slash_dot_dot == 3)
                        while (dst > org_dst && *--dst != '/')
                            /* empty body */;
                    slash_dot_dot = (ch == '/') ? 1 : 0;
                    /* keep the root slash if any */
                    if (!slash_dot_dot && dst == org_dst && *dst == '/')
                        ++dst;
                }
                break;
            case '.':
                if (slash_dot_dot == 1 || slash_dot_dot == 2) {
                    ++slash_dot_dot;
                    break;
                }
                /* fall-through */
            default:
                slash_dot_dot = 0;
            }
        }
        *dst++ = ch;
    } while(ch);
    return (dst - org_dst) - 1;
}

Merci. Ici, c'est sans les trucs de chemin optionnels. pastebin.com/RN5g7g9u
Julian

Cela ne suit aucune recommandation, et est complètement faux par rapport à ce que l'auteur demande («+» n'est pas remplacé par un espace par exemple). La normalisation de chemin n'a rien à voir avec le décodage d'URL. Si vous avez l'intention de normaliser votre chemin, vous devez d'abord diviser votre URL en parties (schéma, autorité, chemin, requête, fragment), puis appliquer l'algorithme de votre choix uniquement sur la partie du chemin.
xryl669

1

les morceaux juteux

#include <ctype.h> // isdigit, tolower

from_hex(char ch) {
  return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

char to_hex(char code) {
  static char hex[] = "0123456789abcdef";
  return hex[code & 15];
}

en notant que

char d = from_hex(hex[0]) << 4 | from_hex(hex[1]);

un péché

// %7B = '{'

char d = from_hex('7') << 4 | from_hex('B');

1

Vous pouvez utiliser la fonction "g_uri_escape_string ()" fournie avec glib.h. https://developer.gnome.org/glib/stable/glib-URI-Functions.html

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
int main() {
    char *uri = "http://www.example.com?hello world";
    char *encoded_uri = NULL;
    //as per wiki (https://en.wikipedia.org/wiki/Percent-encoding)
    char *escape_char_str = "!*'();:@&=+$,/?#[]"; 
    encoded_uri = g_uri_escape_string(uri, escape_char_str, TRUE);
    printf("[%s]\n", encoded_uri);
    free(encoded_uri);

    return 0;
}

compilez-le avec:

gcc encoding_URI.c `pkg-config --cflags --libs glib-2.0`


0

Je sais que la question demande une méthode C ++, mais pour ceux qui pourraient en avoir besoin, j'ai proposé une fonction très courte en C pur pour encoder une chaîne. Il ne crée pas de nouvelle chaîne, mais modifie celle existante, ce qui signifie qu'elle doit avoir une taille suffisante pour contenir la nouvelle chaîne. Très facile à suivre.

void urlEncode(char *string)
{
    char charToEncode;
    int posToEncode;
    while (((posToEncode=strspn(string,"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~"))!=0) &&(posToEncode<strlen(string)))
    {
        charToEncode=string[posToEncode];
        memmove(string+posToEncode+3,string+posToEncode+1,strlen(string+posToEncode));
        string[posToEncode]='%';
        string[posToEncode+1]="0123456789ABCDEF"[charToEncode>>4];
        string[posToEncode+2]="0123456789ABCDEF"[charToEncode&0xf];
        string+=posToEncode+3;
    }
}

0

vous pouvez simplement utiliser la fonction AtlEscapeUrl () de atlutil.h, il suffit de parcourir sa documentation pour savoir comment l'utiliser.


1
cela ne fonctionnerait que sur Windows
kritzikratzi

Oui, j'ai essayé cela sur Windows.
Pratik

-2

J'ai dû le faire dans un projet sans Boost. Alors, j'ai fini par écrire le mien. Je vais juste le mettre sur GitHub: https://github.com/corporateshark/LUrlParser

clParseURL URL = clParseURL::ParseURL( "https://name:pwd@github.com:80/path/res" );

if ( URL.IsValid() )
{
    cout << "Scheme    : " << URL.m_Scheme << endl;
    cout << "Host      : " << URL.m_Host << endl;
    cout << "Port      : " << URL.m_Port << endl;
    cout << "Path      : " << URL.m_Path << endl;
    cout << "Query     : " << URL.m_Query << endl;
    cout << "Fragment  : " << URL.m_Fragment << endl;
    cout << "User name : " << URL.m_UserName << endl;
    cout << "Password  : " << URL.m_Password << endl;
}

Votre lien est vers une bibliothèque qui analyse une URL. Il n'encode pas une URL en%. (Ou du moins, je ne pouvais voir un% nulle part dans la source.) En tant que tel, je ne pense pas que cela réponde à la question.
Martin Bonner soutient Monica
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.