URLs in C ++ kodieren / dekodieren [geschlossen]


85

Kennt jemand einen guten C ++ - Code, der dies tut?


3
Wie wäre es mit einer Antwort?
Gsamaras

Antworten:


81

Ich war neulich mit der Kodierungshälfte dieses Problems konfrontiert. Unzufrieden mit den verfügbaren Optionen, und nach einem Blick auf diesem C - Beispielcode , habe ich beschlossen , meine eigenen C ++ url-encode Funktion zu rollen:

#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();
}

Die Implementierung der Dekodierungsfunktion bleibt dem Leser als Übung überlassen. : P.


1
Ich glaube, es ist allgemeiner (allgemeiner korrekt), '' durch "% 20" zu ersetzen. Ich habe den Code entsprechend aktualisiert. Wenn Sie nicht einverstanden sind, können Sie einen Rollback durchführen.
Josh Kelley

1
Nein, ich stimme zu. setw(0)Ich habe auch die Gelegenheit genutzt, diesen sinnlosen Anruf zu entfernen (zu der Zeit dachte ich, dass die minimale Breite so lange bestehen bleiben würde, bis ich sie wieder geändert habe, aber tatsächlich wird sie nach der nächsten Eingabe zurückgesetzt).
Xperroni

1
Ich musste der Zeile "Escape << '%' << std :: Großbuchstaben << std :: setw (2) << int ((vorzeichenloses Zeichen) c)" std :: uppercase hinzufügen. Für den Fall, dass sich andere Leute fragen, warum dies zum Beispiel% 3a anstelle von% 3A
zurückgibt

2
Es sieht falsch aus, weil UTF-8-Zeichenfolgen nicht unterstützt werden ( w3schools.com/tags/ref_urlencode.asp ). Es scheint nur für Windows-1252
Skywalker13

1
Das Problem war nur isalnum(c), es muss geändert werden zuisalnum((unsigned char) c)
Skywalker13

74

Beantwortung meiner eigenen Frage ...

libcurl hat curl_easy_escape zum Codieren.

Zum Dekodieren curl_easy_unescape


4
Sie sollten diese Antwort akzeptieren, damit sie oben angezeigt wird (und die Leute sie leichter finden können).
Mouagip

Sie müssen Curl verwenden, damit dies funktioniert und Sie müssen den Speicher
freigeben

Verwandte Frage: Warum geht Curls Unescape nicht damit um, '+' in Raum zu ändern? Ist das nicht das Standardverfahren bei der URL-Dekodierung?
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);
}

nicht das beste, aber es funktioniert gut ;-)


5
Natürlich solltest du '%'statt verwenden 37.
John Zwinck

4
Dies konvertiert nicht '+' in Leerzeichen
xryl669

11

cpp-netlib hat Funktionen

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

Sie ermöglichen das einfache Kodieren und Dekodieren von URL-Zeichenfolgen.


2
omg danke. Die Dokumentation zu cpp-netlib ist spärlich. Haben Sie Links zu guten Spickzettel?
user249806

8

Normalerweise funktioniert das Hinzufügen von '%' zum int-Wert eines Zeichens beim Codieren nicht. Der Wert soll dem Hex-Äquivalent entsprechen. zB '/' ist '% 2F' nicht '% 47'.

Ich denke, dies ist die beste und prägnanteste Lösung sowohl für die URL-Codierung als auch für die Decodierung (keine großen Header-Abhängigkeiten).

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"; Wofür ist das Catering? @tormuto @reliasn
KriyenKP

1
@Kriyen wird verwendet, um das codierte HEX mit der führenden Null aufzufüllen, falls es zu einem einzelnen Buchstaben führt. da 0 bis 15 in HEX ist 0 bis F.
tormuto

1
Ich mag diesen Ansatz am besten. +1 für die Verwendung von Standardbibliotheken. Es sind jedoch zwei Probleme zu beheben. Ich bin Tscheche und habe den Buchstaben "ý" verwendet. Ergebnis war "% 0FFFFFFC3% 0FFFFFFBD". Die erste Verwendung des 16-Schalters ist nicht erforderlich, da utf8 garantiert, dass alle nachfolgenden Bytes mit 10 gestartet werden, und mein Multibyte fehlgeschlagen zu sein scheint. Das zweite Problem ist das FF, da nicht alle Computer die gleiche Anzahl von Bits pro Int haben. Die Lösung bestand darin, den 16-Schalter (nicht erforderlich) zu überspringen und die letzten beiden Zeichen aus dem Puffer zu holen. (Ich habe Stringstream verwendet, da ich mich mit einem String-Puffer wohler fühle). Immer noch Punkt gegeben. Wie der Rahmen auch
Volt

@Volt könnten Sie Ihren aktualisierten Code in einer neuen Antwort veröffentlichen? Sie erwähnen die Probleme, aber es sind nicht genug Informationen für eine offensichtliche Lösung.
Gregn3

Diese Antwort hat einige Probleme, weil sie strlen verwendet. Erstens macht dies keinen Sinn, da wir die Größe eines String-Objekts bereits kennen und es daher Zeitverschwendung ist. Viel schlimmer ist jedoch, dass eine Zeichenfolge 0 Bytes enthalten kann, die aufgrund der Zeichenfolge verloren gehen würden. Auch das if (i <16) ist ineffizient, da dies von printf selbst mit "%%% 02X" abgedeckt werden kann. Und schließlich sollte c ein Byte ohne Vorzeichen sein, sonst erhalten Sie den Effekt, den @Volt mit dem führenden '0xFFF ...' beschrieben hat.
Devolus

8

[Nekromant-Modus ein]
Stolperte über diese Frage, als ich nach einer schnellen, modernen, plattformunabhängigen und eleganten Lösung suchte. Cpp-netlib würde der Gewinner sein, aber es hat eine schreckliche Speicheranfälligkeit in der "dekodierten" Funktion. Also habe ich mir die Spirit Qi / Karma-Lösung von Boost ausgedacht.

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;
};

Die Verwendung von oben wie folgt:

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;
}

[Nekromant-Modus aus]

EDIT01: Das Zero-Padding-Zeug wurde behoben - ein besonderer Dank geht an Hartmut Kaiser.
EDIT02: Live on CoLiRu


Was ist die "schreckliche Speicheranfälligkeit" von cpp-netlib? Können Sie eine kurze Erklärung oder einen Link geben?
Craig M. Brandenburg

Es (das Problem) wurde bereits gemeldet, also habe ich es nicht gemeldet und erinnere mich eigentlich nicht ... so etwas wie eine Zugriffsverletzung beim Versuch, eine ungültige Escape-Sequenz zu analysieren, oder so etwas
kreuzerkrieg


Danke fürs klarstellen!
Craig M. Brandenburg


6

Inspiriert von xperroni habe ich einen Decoder geschrieben. Danke für den Hinweis.

#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;
}

Bearbeiten: Nicht benötigter cctype und iomainip enthält entfernt.


1
Der Block "if (c == '%')" muss mehr außerhalb der Grenzen überprüft werden. I [1] und / oder i [2] befinden sich möglicherweise jenseits von text.end (). Ich würde auch "entkommen" in "entführt" umbenennen. "entkommen.Füllung ('0');" ist wahrscheinlich nicht nötig.
Roalz

4

Hinzufügen eines Follow-Ups zu Bills Empfehlung für die Verwendung von libcurl: großartiger Vorschlag und zu aktualisieren:
Nach 3 Jahren ist die Funktion curl_escape veraltet. Für die zukünftige Verwendung ist es daher besser, curl_easy_escape zu verwenden .


4

Ich bin auf diese Frage gestoßen, als ich nach einer API gesucht habe, um die URL in einer Win32 C ++ - App zu dekodieren. Da die Frage die Plattform nicht ganz spezifiziert, ist die Annahme, dass Windows keine schlechte Sache ist.

InternetCanonicalizeUrl ist die API für Windows-Programme. Mehr Infos hier

        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 ( hier ) scheint auch Flags zu haben, um anzugeben, ob die URL dekodiert werden soll


3

Ich konnte hier keine URI-Dekodierung / Unescape finden, die auch 2- und 3-Byte-Sequenzen decodiert. Mit meiner eigenen Hochleistungsversion konvertiert diese On-the-Fly-Eingabe den C-Sting-Eingang in einen String:

#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))und es soll mit -WError bauen.
Janek Olszak

Entschuldigung, aber "hohe Leistung" beim Hinzufügen einzelner Zeichen zu einem wstringist unrealistisch. Zumindest reservegenug Platz, sonst haben Sie die ganze Zeit massive Neuzuweisungen
Felix Dombek


1

Diese Version ist reines C und kann optional den Ressourcenpfad normalisieren. Die Verwendung mit C ++ ist trivial:

#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;
}

Ausgänge:

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

Und die eigentliche Funktion:

#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;
}

Vielen Dank. Hier ist es ohne das optionale Pfadmaterial. pastebin.com/RN5g7g9u
Julian

Dies folgt keiner Empfehlung und ist völlig falsch im Vergleich zu dem, was der Autor verlangt ('+' wird beispielsweise nicht durch Leerzeichen ersetzt). Die Pfadnormalisierung hat nichts mit der URL-Dekodierung zu tun. Wenn Sie beabsichtigen, Ihren Pfad zu normalisieren, sollten Sie zuerst Ihre URL in Teile (Schema, Berechtigung, Pfad, Abfrage, Fragment) aufteilen und dann einen beliebigen Algorithmus nur auf den Pfadteil anwenden.
Xryl669

1

die saftigen Stücke

#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];
}

bemerken, dass

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

wie in

// %7B = '{'

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

1

Sie können die Funktion "g_uri_escape_string ()" verwenden, sofern 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;
}

kompiliere es mit:

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


0

Ich weiß, dass die Frage nach einer C ++ - Methode fragt, aber für diejenigen, die sie möglicherweise benötigen, habe ich eine sehr kurze Funktion in einfachem C entwickelt, um eine Zeichenfolge zu codieren. Es wird keine neue Zeichenfolge erstellt, sondern die vorhandene Zeichenfolge geändert. Dies bedeutet, dass die Größe der neuen Zeichenfolge ausreichen muss. Sehr einfach mitzuhalten.

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

Sie können einfach die Funktion AtlEscapeUrl () von atlutil.h verwenden. Lesen Sie einfach die Dokumentation zur Verwendung.


1
Dies würde nur unter Windows
funktionieren

Ja, ich habe dies unter Windows versucht.
Pratik

-2

Musste es in einem Projekt ohne Boost machen. Also schrieb ich meine eigenen. Ich werde es einfach auf GitHub stellen: 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;
}

Ihr Link führt zu einer Bibliothek, die eine URL analysiert. Eine URL wird nicht% -codiert. (Zumindest konnte ich nirgendwo in der Quelle ein% sehen.) Daher glaube ich nicht, dass dies die Frage beantwortet.
Martin Bonner unterstützt Monica
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.