URL'leri C ++ ile Kodlama / Kod Çözme [kapalı]


Yanıtlar:


83

Geçen gün bu sorunun kodlama yarısıyla karşılaştım. Mevcut seçeneklerden memnun kalmadım ve bu C örnek koduna bir göz attıktan sonra kendi C ++ url kodlama işlevimi kullanmaya karar verdim:

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

Kod çözme işlevinin uygulanması okuyucuya bir alıştırma olarak bırakılmıştır. : P


1
Sanırım daha genel (daha genel olarak doğru) '' yerine "% 20" yazıyor. Kodu buna göre güncelledim; Aynı fikirde değilseniz geri dönmekten çekinmeyin.
Josh Kelley

1
Hayır, katılıyorum. Ayrıca bu anlamsız setw(0)aramayı kaldırma şansını da yakaladım (o zaman, minimum genişliğin, onu geri değiştirene kadar ayarlı kalacağını düşündüm, ancak aslında bir sonraki girişten sonra sıfırlandı).
xperroni

1
"Kaçan << '%' << std :: büyük harf << std :: setw (2) << int ((işaretsiz karakter) c);" satırına std :: büyük harf eklemek zorunda kaldım. Başkalarının neden bunun neden döndürdüğünü merak etmesi durumunda, örneğin% 3A yerine% 3a
gumlym

2
Yanlış görünüyor çünkü UTF-8 dizeleri desteklenmiyor ( w3schools.com/tags/ref_urlencode.asp ). Görünüşe göre sadece Windows-1252
Skywalker13

1
Sorun sadece isalnum(c)şu şekilde değiştirilmeliydiisalnum((unsigned char) c)
Skywalker13

76

Kendi sorumu cevaplıyorum ...

libcurl, kodlama için curl_easy_escape'e sahiptir .

Kod çözme için curl_easy_unescape


4
En üstte görünmesi için (ve insanların daha kolay bulabilmesi için) bu cevabı kabul etmelisiniz.
Mouagip

bunun çalışması için curl kullanmanız ve hafızayı
boşaltmanız gerekiyor

İlgili soru: curl'nin unescape, "+" karakterinin boşluğa dönüştürülmesini neden işlemiyor? URL kodunu çözerken bu standart prosedür değil mi?
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);
}

en iyisi değil, ama iyi çalışıyor ;-)


5
Tabii '%'bunun yerine kullanmalısın 37.
John Zwinck

4
Bu, '+' karakterini boşluğa dönüştürmez
xryl669

11

cpp-netlib'in işlevleri vardır

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

URL dizelerini çok kolay kodlamaya ve çözmeye izin verirler.


2
Aman tanrım teşekkür ederim. cpp-netlib ile ilgili belgeler azdır. İyi hile sayfalarına herhangi bir bağlantınız var mı?
user249806

8

Normalde bir karakterin int değerine '%' eklemek kodlama sırasında işe yaramaz, değerin hex eşdeğerine eşit olduğu varsayılır. örneğin '/', '% 2F', '% 47' değil.

Bunun hem url kodlama hem de kod çözme için en iyi ve özlü çözümler olduğunu düşünüyorum (Fazla başlık bağımlılığı yok).

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"; Bu yemek ne için? @tormuto @reliasn
KriyenKP

1
@Kriyen tek harfle sonuçlanması durumunda kodlanmış HEX'i baştaki sıfır ile doldurmak için kullanılır;
HEX'te

1
Bu yaklaşımı en çok seviyorum. Standart kitaplıkları kullanmak için +1. Düzeltilmesi gereken iki sorun olmasına rağmen. Ben Çek'im ve "ý" harfini kullandım. Sonuç "% 0FFFFFFC3% 0FFFFFFBD" idi. İlk olarak 16 anahtarını kullanmak gerekli değildir, çünkü utf8 tüm sondaki baytları 10 ile başlatmayı garanti eder ve çok baytımın başarısız olduğu görüldü. İkinci sorun FF'dir çünkü tüm bilgisayarlarda int başına aynı miktarda bit yoktur. Düzeltme, 16 anahtarı (gerekli değil) atlamak ve son iki karakteri arabellekten almaktı. (Kendimi daha rahat hissettiğim için stringstream kullandım ve bir string buffer). Yine de puan verdi. Çerçeve gibi
Volt

@ Volt, güncellenmiş kodunuzu yeni bir yanıtta gönderebilir misiniz? Sorunlardan bahsediyorsunuz, ancak bariz bir düzeltme için yeterli bilgi değil.
gregn3

Bu yanıtın bazı sorunları var, çünkü strlen kullanıyor. Birincisi, bu mantıklı değil çünkü biz zaten bir string nesnesinin boyutunu biliyoruz, bu yüzden zaman kaybı. Daha da kötüsü, bir dizenin strlen nedeniyle kaybolan 0 bayt içermesidir. Ayrıca if (i <16) etkisizdir, çünkü bu "%%% 02X" kullanılarak printf tarafından karşılanabilir. Ve son olarak c işaretsiz bayt olmalıdır, aksi takdirde @Volt'un '0xFFF ...' ile tanımladığı etkiyi elde edersiniz.
Devolus

8

[Necromancer modu açık]
Hızlı, modern, platformdan bağımsız ve zarif bir çözüm ararken bu soruya tökezledi. Yukarıdakilerin hiçbiri hoşuna gitmedi, cpp-netlib kazanan olacaktı ancak "kodu çözülmüş" işlevde korkunç bir bellek zayıflığına sahip. Bu yüzden boost'un spirit qi / karma çözümünü buldum.

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

Yukarıdakilerin kullanımı aşağıdaki gibidir:

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

[Necromancer modu kapalı]

EDIT01: sıfır dolgu malzemesi düzeltildi - Hartmut Kaiser'e özel teşekkürler
EDIT02: CoLiRu'da Canlı


"Korkunç bellek zafiyeti" neyin nesi cpp-netlib? Kısa bir açıklama veya bağlantı verebilir misiniz?
Craig M. Brandenburg

Hatırlamıyorum ben daha hiç raporunda bu yüzden aslında O (sorun) zaten bildirildi ... erişim ihlali gibi bir şey geçersiz kaçış dizisi, ya da bir şey ayrıştırmak çalışırken
kreuzerkrieg


Açıkladığınız için teşekkürler!
Craig M. Brandenburg


6

Xperroni'den esinlenerek bir kod çözücü yazdım. İşaretçi için teşekkürler.

#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: Kaldırıldı gereksiz cctype ve iomainip içerir.


1
"If (c == '%')" bloğu daha fazla sınır dışı kontrol gerektirir, i [1] ve / veya i [2] text.end () ötesinde olabilir. Ben de "kaçtı" nın adını "kaçılmamış" olarak yeniden adlandırırdım. "escaped.fill ('0');" muhtemelen gereksizdir.
roalz

Lütfen benim versiyonuma bakın. Daha optimize edilmiştir. pastebin.com/g0zMLpsj
KoD


4

Bir win32 c ++ uygulamasında url'nin kodunu çözmek için bir api ararken bu soruya son verdim. Soru, pencerelerin kötü bir şey olmadığını varsayarak platformu tam olarak belirtmediğinden.

InternetCanonicalizeUrl, Windows programları için API'dir. Daha fazla bilgi burada

        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 ( burada ) da url'nin kodunun çözülüp çözülmeyeceğini belirten bayraklara sahip görünüyor


3

Burada 2 ve 3 bayt dizilerinin de kodunu çözen bir URI kod çözme / çözme bulamadım. Kendi yüksek performanslı versiyonuma katkıda bulunarak, anında c sting girdisini bir wstring'e dönüştürür:

#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))ve -WError ile inşa edilecektir.
Janek Olszak

Üzgünüz, ancak a karakterine tek karakter eklerken "yüksek performans" wstringgerçekçi değil. En azından reserveyeterli alan, aksi takdirde her zaman çok büyük yeniden
tahsislere sahip olacaksınız


1

Bu sürüm saf C'dir ve isteğe bağlı olarak kaynak yolunu normalleştirebilir. C ++ ile kullanmak önemsizdir:

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

Çıktılar:

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

Ve asıl işlev:

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

Teşekkürler. Burada isteğe bağlı yol malzemeleri yok. pastebin.com/RN5g7g9u
Julian

Bu, herhangi bir tavsiyeye uymaz ve yazarın istediği şeyle karşılaştırıldığında tamamen yanlıştır (örneğin '+' yerine boşluk koyulmaz). Yol normalleştirmenin url kod çözme ile ilgisi yoktur. Yolunuzu normalleştirmek niyetindeyseniz, önce URL'nizi parçalara ayırmalı (şema, yetki, yol, sorgu, parça) ve ardından istediğiniz algoritmayı yalnızca yol kısmına uygulamalısınız.
xryl669

1

sulu parçalar

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

not etmek

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

de olduğu gibi

// %7B = '{'

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

1

Glib.h ile sağlanan "g_uri_escape_string ()" işlevini kullanabilirsiniz. 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;
}

şununla derleyin:

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


0

Sorunun bir C ++ yöntemi istediğini biliyorum, ancak buna ihtiyaç duyanlar için, bir dizeyi kodlamak için düz C'de çok kısa bir işlev buldum. Yeni bir dizi oluşturmaz, bunun yerine var olanı değiştirir, yani yeni dizgiyi tutacak kadar büyük olması gerekir. Ayak uydurması çok kolay.

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

atlutil.h'deki AtlEscapeUrl () işlevini basitçe kullanabilirsiniz, sadece nasıl kullanılacağına dair belgelerine bakın.


1
bu sadece pencerelerde çalışır
kritzikratzi

Evet bunu pencerelerde denedim.
Pratik

-2

Boost olmadan bir projede yapmak zorunda kaldı. Böylece, kendi yazımı yazdım. Sadece GitHub'a koyacağım: 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;
}

Bağlantınız bir URL'yi ayrıştıran bir kitaplığa. Bir URL'yi% kodlamaz. (Ya da en azından, kaynakta hiçbir yerde bir% göremedim.) Bu nedenle, bunun soruyu yanıtladığını sanmıyorum.
Martin Bonner Monica'yı
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.