HTTP การเข้ารหัสที่อยู่ URL ใน Java


366

แอปพลิเคชัน Java แบบสแตนด์อโลนของฉันได้รับ URL (ซึ่งชี้ไปที่ไฟล์) จากผู้ใช้และฉันต้องกดและดาวน์โหลด ปัญหาที่ฉันพบคือฉันไม่สามารถเข้ารหัสที่อยู่ URL HTTP ได้อย่างถูกต้อง ...

ตัวอย่าง:

URL:  http://search.barnesandnoble.com/booksearch/first book.pdf

java.net.URLEncoder.encode(url.toString(), "ISO-8859-1");

ส่งคืนฉัน:

http%3A%2F%2Fsearch.barnesandnoble.com%2Fbooksearch%2Ffirst+book.pdf

แต่สิ่งที่ฉันต้องการคือ

http://search.barnesandnoble.com/booksearch/first%20book.pdf

(พื้นที่แทนที่ด้วย% 20)

ฉันเดาURLEncoderว่าไม่ได้ถูกออกแบบมาเพื่อเข้ารหัส URL HTTP ... JavaDoc บอกว่า "คลาสยูทิลิตี้สำหรับการเข้ารหัสฟอร์ม HTML" ... มีวิธีอื่นอีกไหมในการทำเช่นนี้?



พฤติกรรมนี้ถูกต้องทั้งหมด การเข้ารหัส URL คือการเปลี่ยนบางสิ่งให้เป็นสตริงที่สามารถส่งผ่านได้อย่างปลอดภัยเป็นพารามิเตอร์ URL และไม่ได้ตีความว่าเป็น URL เลย ในขณะที่คุณต้องการแปลงเพียงส่วนเล็ก ๆ ของ URL
สตีเฟ่นโฮลท์

คำตอบ:


303

java.net.URIระดับสามารถช่วย; ในเอกสารของ URL ที่คุณพบ

หมายเหตุคลาส URI ทำการหลบหนีของฟิลด์ส่วนประกอบในบางสถานการณ์ วิธีที่แนะนำในการจัดการการเข้ารหัสและถอดรหัส URL คือการใช้ URI

ใช้หนึ่งในตัวสร้างที่มีมากกว่าหนึ่งอาร์กิวเมนต์เช่น:

URI uri = new URI(
    "http", 
    "search.barnesandnoble.com", 
    "/booksearch/first book.pdf",
    null);
URL url = uri.toURL();
//or String request = uri.toString();

(ตัวสร้างอาร์กิวเมนต์แบบเดี่ยวของ URI ไม่หนีอักขระผิดกฎหมาย)


เฉพาะอักขระที่ผิดกฎหมายเท่านั้นที่จะได้รับการยกเว้นโดยโค้ดด้านบน - มันไม่หนีอักขระที่ไม่ใช่ ASCII (ดูความคิดเห็นของ fatih) วิธีสามารถนำมาใช้เพื่อให้ได้สตริงเท่านั้นที่มีตัวอักษร ASCII สหรัฐอเมริกา A:
toASCIIString

URI uri = new URI(
    "http", 
    "search.barnesandnoble.com", 
    "/booksearch/é",
    null);
String request = uri.toASCIIString();

สำหรับ URL ที่มีข้อความค้นหาhttp://www.google.com/ig/api?weather=São Pauloให้ใช้ตัวสร้างเวอร์ชัน 5 พารามิเตอร์:

URI uri = new URI(
        "http", 
        "www.google.com", 
        "/ig/api",
        "weather=São Paulo",
        null);
String request = uri.toASCIIString();

13
โปรดทราบว่าคลาส URI ที่กล่าวถึงในที่นี้มาจาก "org.apache.commons.httpclient.URI" ไม่ใช่ "java.net", "java.net" ไม่ใช่ URI ที่ไม่ยอมรับอักขระที่ผิดกฎหมายเว้นแต่คุณจะใช้ ตัวสร้างที่สร้าง URL จากส่วนประกอบเช่นเดียวกับที่กล่าวไว้ในความคิดเห็นด้านล่าง
Mohamed Faramawi

7
@Mohamed: ชั้นที่ฉันพูดถึงและใช้สำหรับการทดสอบจริง ๆ แล้ว java.net.URI : มันทำงานได้อย่างสมบูรณ์แบบ (Java 1.6) ฉันจะพูดถึงชื่อชั้นที่มีคุณสมบัติครบถ้วนถ้ามันไม่ได้มาตรฐานหนึ่ง Java java.net.URIและจุดเชื่อมโยงไปยังเอกสารของ และตามความคิดเห็นของ Sudhakar มันแก้ไขปัญหาโดยไม่ต้องรวม "ห้องสมุดทั่วไป"!
user85421

1
URI uri = URI ใหม่ ("http", "search.barnesandnoble.com", "/ booksearch / é", null); การหนีตัวอย่างนี้ถูกต้องหรือไม่? สิ่งนี้ควรได้รับการหลบหนีด้วย% escapes
fmucar

@fatih - ถูกต้องขอบคุณ! ปกติแล้วไม่ควรมีปัญหา แต่มีวิธีแก้ปัญหาง่าย ๆ - เกือบเหมือนที่ฉันเขียนไว้ก่อนหน้านี้ ดูการแก้ไขครั้งที่ 2
user85421

@Carlos ขอบคุณสำหรับการแก้ไข ตอนนี้มันจะหลบหนี แต่ไม่ถูกต้องหลบหนี มันควรจะเพิ่ม% ให้กับค่า HEX ของถ่านสำหรับ Path params ความหมายéถ่านควรถูกแปลงเป็น%
e9

91

โปรดได้รับการเตือนว่าคำตอบส่วนใหญ่ข้างต้นนั้นเป็น INCORRECT

URLEncoderชั้นแม้จะเป็นชื่อไม่ได้เป็นสิ่งที่จะต้องอยู่ที่นี่ โชคไม่ดีที่ซันชื่อคลาสนี้อย่างน่ารำคาญ URLEncoderมีไว้สำหรับส่งข้อมูลเป็นพารามิเตอร์ไม่ใช่เพื่อเข้ารหัส URL เอง

กล่าวอีกนัยหนึ่ง"http://search.barnesandnoble.com/booksearch/first book.pdf"คือ URL "http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that"พารามิเตอร์จะยกตัวอย่างเช่น พารามิเตอร์เป็นสิ่งที่คุณจะใช้URLEncoderสำหรับ

ตัวอย่างสองตัวอย่างต่อไปนี้เน้นความแตกต่างระหว่างทั้งสอง

ข้อมูลต่อไปนี้สร้างพารามิเตอร์ที่ไม่ถูกต้องตามมาตรฐาน HTTP โปรดทราบว่าเครื่องหมายและ (&) และเครื่องหมายบวก (+) ถูกเข้ารหัสอย่างไม่ถูกต้อง

uri = new URI("http", null, "www.google.com", 80, 
"/help/me/book name+me/", "MY CRZY QUERY! +&+ :)", null);

// URI: http://www.google.com:80/help/me/book%20name+me/?MY%20CRZY%20QUERY!%20+&+%20:)

ต่อไปนี้จะสร้างพารามิเตอร์ที่ถูกต้องโดยมีการเข้ารหัสแบบสอบถามอย่างถูกต้อง หมายเหตุช่องว่างเครื่องหมายและเครื่องหมายบวก

uri = new URI("http", null, "www.google.com", 80, "/help/me/book name+me/", URLEncoder.encode("MY CRZY QUERY! +&+ :)", "UTF-8"), null);

// URI: http://www.google.com:80/help/me/book%20name+me/?MY+CRZY+QUERY%2521+%252B%2526%252B+%253A%2529

2
ถูกต้องตัวสร้าง URI เข้ารหัส querystring ตามเอกสารdocs.oracle.com/javase/1.4.2/docs/api/java/net/… , java.lang.String, java.lang.String, int , java.lang.String, java.lang.String, java.lang.String)
madoke

8
@Draemon คำตอบนั้นถูกต้อง แต่ใช้สตริงการสืบค้นด้วยวิธีที่ไม่ธรรมดา query = URLEncoder.encode(key) + "=" + URLEncoder.encode(value)เป็นตัวอย่างที่มากกว่าปกติอาจจะมี เอกสารกล่าวเพียงว่า "อักขระใด ๆ ที่ไม่ใช่อักขระ URI ที่ถูกต้องตามกฎหมาย"
tc

1
ฉันเห็นด้วยกับแมตต์ที่นี่ หากคุณพิมพ์ URL นี้: " google.com/help/me/bookชื่อ + me /? MY CRZY QUERY! + & + :)" ในเบราว์เซอร์จะเข้ารหัสช่องว่างโดยอัตโนมัติ แต่ใช้ "&" เป็นค่าข้อความค้นหา ตัวแยกและ "+" หายไป
arcot

80

ฉันจะเพิ่มข้อเสนอแนะหนึ่งข้อสำหรับผู้ใช้ Android ที่นี่ คุณสามารถทำสิ่งนี้เพื่อหลีกเลี่ยงการได้รับห้องสมุดภายนอกใด ๆ นอกจากนี้โซลูชันการค้นหา / แทนที่อักขระทั้งหมดที่แนะนำในคำตอบบางข้อด้านบนนั้นเต็มไปด้วยอันตรายและควรหลีกเลี่ยง

ลองดูสิ:

String urlStr = "http://abc.dev.domain.com/0007AC/ads/800x480 15sec h.264.mp4";
URL url = new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
url = uri.toURL();

คุณสามารถเห็นได้ว่าใน URL นี้ฉันต้องมีการเว้นวรรคเหล่านั้นเพื่อให้ฉันสามารถใช้มันสำหรับการร้องขอ

นี่เป็นการใช้ประโยชน์จากคุณสมบัติสองสามอย่างที่มีให้คุณในคลาส Android ก่อนอื่นคลาส URL สามารถแบ่ง URL เป็นส่วนประกอบที่เหมาะสมดังนั้นคุณไม่จำเป็นต้องทำการค้นหา / แทนที่สตริงใด ๆ ประการที่สองวิธีนี้ใช้ประโยชน์จากคุณลักษณะคลาส URI ในการหลีกเลี่ยงส่วนประกอบได้อย่างถูกต้องเมื่อคุณสร้าง URI ผ่านส่วนประกอบแทนที่จะเป็นสตริงเดียว

ความสวยงามของวิธีการนี้คือคุณสามารถใช้สตริง URL ที่ถูกต้องและใช้งานได้โดยไม่จำเป็นต้องมีความรู้พิเศษใด ๆ ด้วยตัวคุณเอง


3
วิธีการที่ดี แต่ฉันอยากจะชี้ให้เห็นว่ารหัสนี้ไม่ได้ป้องกันการเข้ารหัสซ้ำเช่น% 20 ถูกเข้ารหัสเป็น% 2520 คำตอบของสกอตต์ไม่ได้รับความทุกข์ทรมานจากสิ่งนี้
nattster

2
ไม่สามารถจัดการ#ได้
Alston

หรือถ้าคุณแค่ต้องการอ้างถึง path: URI ใหม่ (null, null, "/ path with ช่องว่าง", null, null) .toString ()
user1050755

1
@Stallman หากชื่อไฟล์ของคุณมี # คลาส URL จะใส่ไว้ใน "ref" (เทียบเท่ากับ "fragment" ในคลาส URI) คุณสามารถตรวจสอบว่า URL.getRef () ส่งคืนบางสิ่งที่อาจถือว่าเป็นส่วนหนึ่งของเส้นทางและส่งผ่าน URL.getPath () + "#" + URL.getRef () เป็นพารามิเตอร์ "path" และ null เป็น "fragment" พารามิเตอร์ "ของตัวสร้างพารามิเตอร์คลาส 7 ของ URI โดยค่าเริ่มต้นสตริงหลังจาก # จะถือว่าเป็นการอ้างอิง (หรือยึด)
gouessej

49

วิธีการแก้ปัญหาที่ฉันพัฒนาและมีเสถียรภาพมากกว่าอื่น ๆ :

public class URLParamEncoder {

    public static String encode(String input) {
        StringBuilder resultStr = new StringBuilder();
        for (char ch : input.toCharArray()) {
            if (isUnsafe(ch)) {
                resultStr.append('%');
                resultStr.append(toHex(ch / 16));
                resultStr.append(toHex(ch % 16));
            } else {
                resultStr.append(ch);
            }
        }
        return resultStr.toString();
    }

    private static char toHex(int ch) {
        return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
    }

    private static boolean isUnsafe(char ch) {
        if (ch > 128 || ch < 0)
            return true;
        return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
    }

}

3
ที่ต้องให้คุณแบ่ง URL ออกเป็นชิ้น ๆ ไม่มีวิธีที่คอมพิวเตอร์จะรู้ได้ว่าส่วนใดของ URL ที่จะเข้ารหัส ดูการแก้ไขด้านบนของฉัน
fmucar

4
@fmucar ขอบคุณสำหรับรหัสชิ้นนั้น! ควรสังเกตว่านี่ไม่ใช่ UTF-8 หากต้องการรับ UTF-8 เพียงดำเนินการอินพุตล่วงหน้าString utf8Input = new String(Charset.forName("UTF-8").encode(input).array());(จากที่นี่ )
letmaik

1
โซลูชันนี้จะเข้ารหัสส่วน "http: //" ไปยัง "http% 3A% 2F% 2F" ซึ่งเป็นคำถามเริ่มต้นที่พยายามหลีกเลี่ยง
Benjamin Piette

2
คุณส่งเฉพาะสิ่งที่คุณต้องการในการเข้ารหัสไม่ใช่ URL ทั้งหมด ไม่มีวิธีการส่งผ่านสตริง URL ทั้งหมดและคาดว่าการเข้ารหัสที่ถูกต้อง ในทุกกรณีคุณจะต้องแยก URL ออกเป็นส่วน ๆ
fmucar

2
ฉันมีปัญหากับคำตอบนี้เพราะมันไม่ได้เข้ารหัสตัวอักษรที่ไม่ปลอดภัยกับ UTF-8 .. อาจขึ้นอยู่กับแอพพลิเคชั่นเพียร์
Tarnschaf

36

หากคุณมี URL คุณสามารถส่ง url.toString () ไปยังวิธีนี้ได้ การถอดรหัสครั้งแรกเพื่อหลีกเลี่ยงการเข้ารหัสสองครั้ง (ตัวอย่างเช่นการเข้ารหัสช่องว่างส่งผลให้% 20 และการเข้ารหัสเครื่องหมายเปอร์เซ็นต์ส่งผลให้เป็น% 25 ดังนั้นการเข้ารหัสสองครั้งจะเปลี่ยนช่องว่างเป็น% 2520) จากนั้นใช้ URI ตามที่อธิบายไว้ข้างต้นเพิ่มในทุกส่วนของ URL (เพื่อไม่ให้คุณทิ้งพารามิเตอร์การสืบค้น)

public URL convertToURLEscapingIllegalCharacters(String string){
    try {
        String decodedURL = URLDecoder.decode(string, "UTF-8");
        URL url = new URL(decodedURL);
        URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); 
        return uri.toURL(); 
    } catch (Exception ex) {
        ex.printStackTrace();
        return null;
    }
}

1
URLDecoder.decode (สตริง "UTF-8") ล้มเหลวด้วย IllegalArgumentException เมื่อคุณส่งสตริงเป็น " google.co.in/search?q=123%!123 " นี่เป็น URL ที่ถูกต้อง ฉันเดาว่า API นี้จะไม่ทำงานเมื่อใช้%% เป็นข้อมูลแทนที่จะเป็นอักขระการเข้ารหัส
MediumOne

26

ใช่การเข้ารหัส URL จะเข้ารหัสสตริงนั้นเพื่อให้ผ่านอย่างถูกต้องใน URL ไปยังปลายทางสุดท้าย ตัวอย่างเช่นคุณไม่สามารถมีhttp://stackoverflow.com?url=http://yyy.com UrlEncoding พารามิเตอร์จะแก้ไขค่าพารามิเตอร์นั้น

ดังนั้นฉันมีสองทางเลือกสำหรับคุณ:

  1. คุณมีการเข้าถึงเส้นทางแยกจากโดเมนหรือไม่ ถ้าเป็นเช่นนั้นคุณอาจจะเพียงแค่ UrlEncode เส้นทาง อย่างไรก็ตามหากไม่ใช่กรณีนี้ตัวเลือกที่ 2 อาจเหมาะสำหรับคุณ

  2. รับ commons-httpclient-3.1 นี่คือคลาส URIUtil:

    System.out.println (URIUtil.encodePath (" http://example.com/x y", "ISO-8859-1"));

สิ่งนี้จะแสดงผลลัพธ์ที่คุณต้องการอย่างแน่นอนเพราะมันจะเข้ารหัสเฉพาะส่วนของเส้นทางของ URI เท่านั้น

FYI คุณจะต้องใช้ตัวแปลงสัญญาณทั่วไปและการบันทึกแบบทั่วไปสำหรับวิธีนี้เพื่อให้ทำงานได้ในขณะใช้งานจริง


Sidenote apache คอมมอนหยุดการบำรุงรักษา URIUtil ในสาขา 4.x แนะนำให้คุณใช้คลาส URI ของ JDK แทน หมายความว่าคุณต้องแยกสตริงด้วยตัวเอง
Nicholi

2) ตรงตามที่แนะนำไว้ที่นี่stackoverflow.com/questions/5330104/ …ฉันใช้URIUtilวิธีแก้ปัญหาด้วย
ถึงกระ

11

Nitpicking: สตริงที่มีอักขระช่องว่างตามคำนิยามไม่ใช่ URI ดังนั้นสิ่งที่คุณกำลังมองหาเป็นรหัสที่ใช้การ Escape URI ที่กำหนดไว้ในมาตรา 2.1 ของ RFC 3986


เราต้องการคำตอบว่า "อย่างไร" ไม่ใช่ "อะไร"
shinzou

11

น่าเสียดายที่org.apache.commons.httpclient.util.URIUtilเลิกใช้แล้วและreplacement org.apache.commons.codec.net.URLCodecโค้ดนั้นเหมาะสมกับการโพสต์แบบฟอร์มไม่ใช่ URL จริง ดังนั้นฉันจึงต้องเขียนฟังก์ชั่นของตัวเองซึ่งองค์ประกอบเดียว (ไม่เหมาะสำหรับสตริงการสืบค้นทั้งหมดที่มี? และ & s)

public static String encodeURLComponent(final String s)
{
  if (s == null)
  {
    return "";
  }

  final StringBuilder sb = new StringBuilder();

  try
  {
    for (int i = 0; i < s.length(); i++)
    {
      final char c = s.charAt(i);

      if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
          ((c >= '0') && (c <= '9')) ||
          (c == '-') ||  (c == '.')  || (c == '_') || (c == '~'))
      {
        sb.append(c);
      }
      else
      {
        final byte[] bytes = ("" + c).getBytes("UTF-8");

        for (byte b : bytes)
        {
          sb.append('%');

          int upper = (((int) b) >> 4) & 0xf;
          sb.append(Integer.toHexString(upper).toUpperCase(Locale.US));

          int lower = ((int) b) & 0xf;
          sb.append(Integer.toHexString(lower).toUpperCase(Locale.US));
        }
      }
    }

    return sb.toString();
  }
  catch (UnsupportedEncodingException uee)
  {
    throw new RuntimeException("UTF-8 unsupported!?", uee);
  }
}

มาเลยต้องมีห้องสมุดที่ทำสิ่งนี้
shinzou

9

การเข้ารหัส URL สามารถเข้ารหัส URL HTTP ได้อย่างดีเยี่ยม สตริงที่คุณส่งผ่าน " http://search.barnesandnoble.com/booksearch/first book.pdf" ได้รับการเข้ารหัสอย่างถูกต้องและสมบูรณ์ในรูปแบบการเข้ารหัส URL คุณสามารถส่งสตริง gobbledigook ที่มีความยาวทั้งหมดที่คุณได้รับกลับมาเป็นพารามิเตอร์ใน URL และสามารถถอดรหัสกลับไปยังสตริงที่คุณส่งผ่าน

ดูเหมือนว่าคุณต้องการทำบางสิ่งที่แตกต่างจากการส่ง URL ทั้งหมดเป็นพารามิเตอร์ จากสิ่งที่ฉันรวบรวมคุณกำลังพยายามสร้าง URL การค้นหาที่ดูเหมือน " http://search.barnesandnoble.com/booksearch/whก็ตามTheUserPassesIn " สิ่งเดียวที่คุณต้องเข้ารหัสคือบิต "whatTheUserPassesIn" ดังนั้นสิ่งที่คุณต้องทำคือสิ่งนี้:

String url = "http://search.barnesandnoble.com/booksearch/" + 
       URLEncoder.encode(userInput,"UTF-8");

สิ่งนี้ควรสร้างบางสิ่งที่ค่อนข้างถูกต้องสำหรับคุณ


17
ที่จะแทนที่ช่องว่างใน userInput ด้วย "+" โปสเตอร์ต้องการให้แทนที่ด้วย "% 20"
vocaro

@vocaro: นั่นเป็นจุดที่ดีมาก URLEncoder หนีออกมาเช่นข้อโต้แย้งเป็นพารามิเตอร์แบบสอบถามไม่เหมือนส่วนที่เหลือของ URL
Brandon Yarbrough

9

หากใครไม่ต้องการเพิ่มการพึ่งพาโครงการของพวกเขาฟังก์ชั่นเหล่านี้อาจเป็นประโยชน์

เราผ่านส่วน 'เส้นทาง' ของ URL ของเราไปที่นี่ คุณอาจไม่ต้องการส่ง URL แบบเต็มในรูปแบบของพารามิเตอร์

/**
 * Percent-encodes a string so it's suitable for use in a URL Path (not a query string / form encode, which uses + for spaces, etc)
 */
public static String percentEncode(String encodeMe) {
    if (encodeMe == null) {
        return "";
    }
    String encoded = encodeMe.replace("%", "%25");
    encoded = encoded.replace(" ", "%20");
    encoded = encoded.replace("!", "%21");
    encoded = encoded.replace("#", "%23");
    encoded = encoded.replace("$", "%24");
    encoded = encoded.replace("&", "%26");
    encoded = encoded.replace("'", "%27");
    encoded = encoded.replace("(", "%28");
    encoded = encoded.replace(")", "%29");
    encoded = encoded.replace("*", "%2A");
    encoded = encoded.replace("+", "%2B");
    encoded = encoded.replace(",", "%2C");
    encoded = encoded.replace("/", "%2F");
    encoded = encoded.replace(":", "%3A");
    encoded = encoded.replace(";", "%3B");
    encoded = encoded.replace("=", "%3D");
    encoded = encoded.replace("?", "%3F");
    encoded = encoded.replace("@", "%40");
    encoded = encoded.replace("[", "%5B");
    encoded = encoded.replace("]", "%5D");
    return encoded;
}

/**
 * Percent-decodes a string, such as used in a URL Path (not a query string / form encode, which uses + for spaces, etc)
 */
public static String percentDecode(String encodeMe) {
    if (encodeMe == null) {
        return "";
    }
    String decoded = encodeMe.replace("%21", "!");
    decoded = decoded.replace("%20", " ");
    decoded = decoded.replace("%23", "#");
    decoded = decoded.replace("%24", "$");
    decoded = decoded.replace("%26", "&");
    decoded = decoded.replace("%27", "'");
    decoded = decoded.replace("%28", "(");
    decoded = decoded.replace("%29", ")");
    decoded = decoded.replace("%2A", "*");
    decoded = decoded.replace("%2B", "+");
    decoded = decoded.replace("%2C", ",");
    decoded = decoded.replace("%2F", "/");
    decoded = decoded.replace("%3A", ":");
    decoded = decoded.replace("%3B", ";");
    decoded = decoded.replace("%3D", "=");
    decoded = decoded.replace("%3F", "?");
    decoded = decoded.replace("%40", "@");
    decoded = decoded.replace("%5B", "[");
    decoded = decoded.replace("%5D", "]");
    decoded = decoded.replace("%25", "%");
    return decoded;
}

และการทดสอบ:

@Test
public void testPercentEncode_Decode() {
    assertEquals("", percentDecode(percentEncode(null)));
    assertEquals("", percentDecode(percentEncode("")));

    assertEquals("!", percentDecode(percentEncode("!")));
    assertEquals("#", percentDecode(percentEncode("#")));
    assertEquals("$", percentDecode(percentEncode("$")));
    assertEquals("@", percentDecode(percentEncode("@")));
    assertEquals("&", percentDecode(percentEncode("&")));
    assertEquals("'", percentDecode(percentEncode("'")));
    assertEquals("(", percentDecode(percentEncode("(")));
    assertEquals(")", percentDecode(percentEncode(")")));
    assertEquals("*", percentDecode(percentEncode("*")));
    assertEquals("+", percentDecode(percentEncode("+")));
    assertEquals(",", percentDecode(percentEncode(",")));
    assertEquals("/", percentDecode(percentEncode("/")));
    assertEquals(":", percentDecode(percentEncode(":")));
    assertEquals(";", percentDecode(percentEncode(";")));

    assertEquals("=", percentDecode(percentEncode("=")));
    assertEquals("?", percentDecode(percentEncode("?")));
    assertEquals("@", percentDecode(percentEncode("@")));
    assertEquals("[", percentDecode(percentEncode("[")));
    assertEquals("]", percentDecode(percentEncode("]")));
    assertEquals(" ", percentDecode(percentEncode(" ")));

    // Get a little complex
    assertEquals("[]]", percentDecode(percentEncode("[]]")));
    assertEquals("a=d%*", percentDecode(percentEncode("a=d%*")));
    assertEquals(")  (", percentDecode(percentEncode(")  (")));
    assertEquals("%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25",
                    percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %"));
    assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode(
                    "%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25"));

    assertEquals("%23456", percentDecode(percentEncode("%23456")));

}

ขอบคุณสำหรับสิ่งนี้ แต่สิ่งที่ฉันต้องทำเพื่อเข้ารหัสช่องว่าง -> ใช้% 20 แทนตามตัวอย่างของคุณ
N00b Pr0grammer

อัปเดตเพื่อบัญชีช่องว่างเป็น% 20
Cuga

7

ยังคงมีปัญหาหากคุณได้รับการเข้ารหัส "/" (% 2F) ใน URL ของคุณ

RFC 3986 - ส่วนที่ 2.2 กล่าวว่า: "หากข้อมูลสำหรับองค์ประกอบ URI จะขัดแย้งกับจุดประสงค์ของตัวละครที่สงวนไว้เป็นตัวคั่นข้อมูลที่ขัดแย้งจะต้องเข้ารหัสเป็นเปอร์เซ็นต์ก่อนที่จะเกิด URI" (RFC 3986 - ส่วนที่ 2.2)

แต่มีปัญหากับ Tomcat:

http://tomcat.apache.org/security-6.html - แก้ไขใน Apache Tomcat 6.0.10

สำคัญ: Directory traversal CVE-2007-0450

Tomcat อนุญาตให้ '\', '% 2F' และ '% 5C' [... ]

คุณสมบัติของระบบ Java ต่อไปนี้ได้รับการเพิ่มใน Tomcat เพื่อให้การควบคุมเพิ่มเติมของการจัดการตัวคั่นเส้นทางใน URL (ตัวเลือกทั้งสองเริ่มต้นเป็นเท็จ):

  • org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH: true | false
  • org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH: true | false

เนื่องจากความเป็นไปไม่ได้ที่จะรับประกันว่า Tomcat จะจัดการ URL ทั้งหมดเนื่องจากอยู่ในพร็อกซีเซิร์ฟเวอร์ Tomcat ควรมีความปลอดภัยเสมอเสมือนว่าไม่มีการ จำกัด การเข้าถึงบริบทของพร็อกซี

ผลกระทบ: 6.0.0-6.0.9

ดังนั้นหากคุณมี URL ที่มีอักขระ% 2F Tomcat จะส่งกลับ: "400 URI ไม่ถูกต้อง: noSlash"

คุณสามารถเปลี่ยนข้อผิดพลาดในสคริปต์เริ่มต้น Tomcat:

set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%   -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true 

7

ฉันอ่านคำตอบก่อนหน้าเพื่อเขียนวิธีการของตัวเองเพราะฉันไม่สามารถทำงานได้อย่างถูกต้องโดยใช้วิธีแก้ปัญหาของคำตอบก่อนหน้านี้มันดูดีสำหรับฉัน แต่ถ้าคุณสามารถหา URL ที่ไม่สามารถใช้งานได้โปรดแจ้งให้เราทราบ

public static URL convertToURLEscapingIllegalCharacters(String toEscape) throws MalformedURLException, URISyntaxException {
            URL url = new URL(toEscape);
            URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
            //if a % is included in the toEscape string, it will be re-encoded to %25 and we don't want re-encoding, just encoding
            return new URL(uri.toString().replace("%25", "%"));
}

4

ฉันเห็นด้วยกับแมตต์ อันที่จริงฉันไม่เคยเห็นมันอธิบายอย่างชัดเจนในแบบฝึกหัด แต่สิ่งหนึ่งคือวิธีเข้ารหัสเส้นทาง URL และอีกวิธีหนึ่งคือวิธีเข้ารหัสพารามิเตอร์ที่ต่อท้าย URL (ส่วนแบบสอบถามด้านหลัง "? " สัญลักษณ์). พวกเขาใช้การเข้ารหัสที่คล้ายกัน แต่ไม่เหมือนกัน

พิเศษสำหรับการเข้ารหัสของอักขระช่องว่าง เส้นทาง URL ต้องการให้เข้ารหัสเป็น% 20 ในขณะที่ส่วนแบบสอบถามอนุญาตให้% 20 และเครื่องหมาย "+" ความคิดที่ดีที่สุดคือการทดสอบด้วยตนเองกับเว็บเซิร์ฟเวอร์ของเราโดยใช้เว็บเบราว์เซอร์

สำหรับทั้งสองกรณีฉันมักจะเข้ารหัสส่วนประกอบตามส่วนประกอบไม่ใช่สตริงทั้งหมด อันที่จริง URLEncoder อนุญาตให้ส่วนแบบสอบถาม สำหรับส่วนของพา ธ คุณสามารถใช้คลาส URI ได้แม้ว่าในกรณีนี้มันจะถามถึงสตริงทั้งหมดไม่ใช่องค์ประกอบเดียว

อย่างไรก็ตามฉันเชื่อว่าวิธีที่ดีที่สุดในการหลีกเลี่ยงปัญหาเหล่านี้คือการใช้การออกแบบที่ไม่ขัดแย้งส่วนตัว อย่างไร? ตัวอย่างเช่นฉันจะไม่ตั้งชื่อไดเรกทอรีหรือพารามิเตอร์โดยใช้อักขระอื่นนอกเหนือจาก aZ, AZ, 0-9 และ _ ด้วยวิธีนี้ความต้องการเพียงอย่างเดียวคือการเข้ารหัสค่าของทุกพารามิเตอร์เนื่องจากอาจมาจากอินพุตของผู้ใช้และไม่รู้จักอักขระที่ใช้


2
โค้ดตัวอย่างที่ใช้ URL ในคำถามน่าจะเป็นคำตอบที่ดีสำหรับคุณ
Martin Serrano



2

นอกเหนือจากคำตอบของ Carlos Heuberger: หากต้องการความแตกต่างจากค่าเริ่มต้น (80) ควรใช้ constructor 7 param:

URI uri = new URI(
        "http",
        null, // this is for userInfo
        "www.google.com",
        8080, // port number as int
        "/ig/api",
        "weather=São Paulo",
        null);
String request = uri.toASCIIString();

2

ฉันเอาเนื้อหาด้านบนและเปลี่ยนมันเล็กน้อย ฉันชอบตรรกะในเชิงบวกก่อนและฉันคิดว่า HashSet อาจให้ประสิทธิภาพที่ดีกว่าตัวเลือกอื่น ๆ เช่นการค้นหาผ่านสตริง แม้ว่าฉันไม่แน่ใจว่าโทษของออโตบอทนั้นมีค่าหรือไม่ แต่ถ้าคอมไพเลอร์ปรับให้เหมาะกับ ASCII chars ค่าใช้จ่ายในการชกมวยจะต่ำ

/***
 * Replaces any character not specifically unreserved to an equivalent 
 * percent sequence.
 * @param s
 * @return
 */
public static String encodeURIcomponent(String s)
{
    StringBuilder o = new StringBuilder();
    for (char ch : s.toCharArray()) {
        if (isSafe(ch)) {
            o.append(ch);
        }
        else {
            o.append('%');
            o.append(toHex(ch / 16));
            o.append(toHex(ch % 16));
        }
    }
    return o.toString();
}

private static char toHex(int ch)
{
    return (char)(ch < 10 ? '0' + ch : 'A' + ch - 10);
}

// https://tools.ietf.org/html/rfc3986#section-2.3
public static final HashSet<Character> UnreservedChars = new HashSet<Character>(Arrays.asList(
        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
        '0','1','2','3','4','5','6','7','8','9',
        '-','_','.','~'));
public static boolean isSafe(char ch)
{
    return UnreservedChars.contains(ch);
}

1

ใช้โซลูชัน Java มาตรฐานต่อไปนี้ (ส่งผ่าน 100 ชุดทดสอบที่จัดทำโดยWeb Plattform Tests ):

0. ทดสอบว่า URL ที่ถูกเข้ารหัสแล้ว

1.แยก URL ออกเป็นส่วนโครงสร้าง ใช้java.net.URL สำหรับมัน

2. เข้ารหัสส่วนโครงสร้างอย่างถูกต้อง!

3.ใช้IDN.toASCII(putDomainNameHere)ในการPunycodeเข้ารหัสชื่อโฮสต์!

4.ใช้java.net.URI.toASCIIString()การเข้ารหัสเปอร์เซ็นต์เข้ารหัส Unicode Unicode - (ดีกว่าน่าจะเป็น NFKC!)

ค้นหาเพิ่มเติมได้ที่นี่: https://stackoverflow.com/a/49796882/1485527


0

ฉันได้สร้างโครงการใหม่เพื่อช่วยสร้าง URL HTTP ไลบรารีจะเข้ารหัสเส้นทางของ URL โดยอัตโนมัติและพารามิเตอร์การสืบค้น

คุณสามารถดูแหล่งที่มาและดาวน์โหลดไบนารีได้ที่https://github.com/Widen/urlbuilder

ตัวอย่าง URL ในคำถามนี้:

new UrlBuilder("search.barnesandnoble.com", "booksearch/first book.pdf").toString()

ผลิต

http://search.barnesandnoble.com/booksearch/first%20book.pdf


0

ผมมีปัญหาเดียวกัน. แก้ไขสิ่งนี้โดยการปลด:

android.net.Uri.encode(urlString, ":/");

มันเข้ารหัสสตริง แต่ข้าม ":" และ "/"


0

ฉันใช้สิ่งนี้

org.apache.commons.text.StringEscapeUtils.escapeHtml4("my text % & < >");

เพิ่มการพึ่งพานี้

 <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.8</version>
    </dependency>

-2

ฉันพัฒนาห้องสมุดที่ทำหน้าที่นี้: กาลิเทีย แยกวิเคราะห์ URL เช่นเดียวกับที่เว็บเบราว์เซอร์ทำ กล่าวคือหาก URL ทำงานในเบราว์เซอร์จะมีการแยกวิเคราะห์อย่างถูกต้องโดยกาลิมั

ในกรณีนี้:

// Parse
io.mola.galimatias.URL.parse(
    "http://search.barnesandnoble.com/booksearch/first book.pdf"
).toString()

จะให้: http://search.barnesandnoble.com/booksearch/first%20book.pdf. ของหลักสูตรนี้เป็นกรณีที่ง่าย java.net.URIแต่มันจะทำงานกับสิ่งที่เกินกว่าวิธี

คุณสามารถตรวจสอบได้ที่: https://github.com/smola/galimatias


-3

คุณสามารถใช้ฟังก์ชั่นเช่นนี้ เติมและแก้ไขตามความต้องการของคุณ:

/**
     * Encode URL (except :, /, ?, &, =, ... characters)
     * @param url to encode
     * @param encodingCharset url encoding charset
     * @return encoded URL
     * @throws UnsupportedEncodingException
     */
    public static String encodeUrl (String url, String encodingCharset) throws UnsupportedEncodingException{
            return new URLCodec().encode(url, encodingCharset).replace("%3A", ":").replace("%2F", "/").replace("%3F", "?").replace("%3D", "=").replace("%26", "&");
    }

ตัวอย่างการใช้งาน:

String urlToEncode = ""http://www.growup.com/folder/intérieur-à_vendre?o=4";
Utils.encodeUrl (urlToEncode , "UTF-8")

ผลลัพธ์คือ: http://www.growup.com/folder/int%C3%A9rieur-%C3%A0_vendre?o=4


1
คำตอบนี้ไม่สมบูรณ์โดยไม่ต้อง URLCodec
มาร์ควิสแห่ง Lorne

upvote สำหรับ. แทนที่ () การผูกมัดมันไม่เหมาะ แต่ก็เพียงพอสำหรับกรณีการใช้งานเฉพาะกิจขั้นพื้นฐาน
svarog

-5

String url = "" http://search.barnesandnoble.com/booksearch/ ;

นี่คงเป็นสิ่งที่ฉันคาดเดาและมีเพียงการเปลี่ยนชื่อไฟล์เท่านั้น

ชื่อไฟล์สตริง; // รับชื่อไฟล์

String urlEnc = url + fileName.replace ("", "% 20");


2
แล้วตัวละครผิดกฎหมายอื่น ๆ ล่ะ?
มาร์ควิสแห่ง Lorne

-7

เกี่ยวกับ:

String สาธารณะ UrlEncode (สตริง in_) {

String retVal = "";

try {
    retVal = URLEncoder.encode(in_, "UTF8");
} catch (UnsupportedEncodingException ex) {
    Log.get().exception(Log.Level.Error, "urlEncode ", ex);
}

return retVal;

}


ไม่สามารถใช้ URLEncoder เพื่อหลีกเลี่ยงอักขระ URL ivalid การเข้ารหัสแบบฟอร์มเท่านั้น
อาร์เชอ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.