From c7db80cfb8b4186eaf4ea01e57c8060c9119ddd7 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 8 Sep 2022 12:34:12 -0500 Subject: [PATCH] most uri pass docomo and exotic fail --- .../result/ProductParsedResultTestCase.rs | 22 +-- src/client/result/ResultParser.rs | 15 +- src/client/result/URIParsedResult.java | 86 --------- src/client/result/URIParsedResult.rs | 102 +++++++++++ .../result/URIParsedResultTestCase.java | 140 -------------- src/client/result/URIParsedResultTestCase.rs | 173 ++++++++++++++++++ src/client/result/URIResultParser.java | 81 -------- src/client/result/URIResultParser.rs | 118 ++++++++++++ ...ResultParser.java => URLTOResultParser.rs} | 46 +++-- src/client/result/mod.rs | 10 +- 10 files changed, 449 insertions(+), 344 deletions(-) delete mode 100644 src/client/result/URIParsedResult.java create mode 100644 src/client/result/URIParsedResult.rs delete mode 100644 src/client/result/URIParsedResultTestCase.java create mode 100644 src/client/result/URIParsedResultTestCase.rs delete mode 100644 src/client/result/URIResultParser.java create mode 100644 src/client/result/URIResultParser.rs rename src/client/result/{URLTOResultParser.java => URLTOResultParser.rs} (51%) diff --git a/src/client/result/ProductParsedResultTestCase.rs b/src/client/result/ProductParsedResultTestCase.rs index b1b4919..c5b7240 100644 --- a/src/client/result/ProductParsedResultTestCase.rs +++ b/src/client/result/ProductParsedResultTestCase.rs @@ -33,21 +33,21 @@ use crate::{BarcodeFormat, RXingResult, client::result::{ParsedRXingResult, Pars use super::ResultParser; #[test] - fn testProduct() { - doTest("123456789012", "123456789012", BarcodeFormat::UPC_A); - doTest("00393157", "00393157", BarcodeFormat::EAN_8); - doTest("5051140178499", "5051140178499", BarcodeFormat::EAN_13); - doTest("01234565", "012345000065", BarcodeFormat::UPC_E); + fn test_product() { + do_test("123456789012", "123456789012", BarcodeFormat::UPC_A); + do_test("00393157", "00393157", BarcodeFormat::EAN_8); + do_test("5051140178499", "5051140178499", BarcodeFormat::EAN_13); + do_test("01234565", "012345000065", BarcodeFormat::UPC_E); } - fn doTest( contents:&str, normalized:&str, format:BarcodeFormat) { - let fakeRXingResult = RXingResult::new(contents, Vec::new(), Vec::new(), format); - let result = ResultParser::parseRXingResult(&fakeRXingResult); + fn do_test( contents:&str, normalized:&str, format:BarcodeFormat) { + let fake_rxing_result = RXingResult::new(contents, Vec::new(), Vec::new(), format); + let result = ResultParser::parseRXingResult(&fake_rxing_result); assert_eq!(ParsedRXingResultType::PRODUCT, result.getType()); - if let ParsedClientResult::ProductResult(productRXingResult) = result { - assert_eq!(contents, productRXingResult.getProductID()); - assert_eq!(normalized, productRXingResult.getNormalizedProductID()); + if let ParsedClientResult::ProductResult(product_rxing_result) = result { + assert_eq!(contents, product_rxing_result.getProductID()); + assert_eq!(normalized, product_rxing_result.getNormalizedProductID()); }else{ panic!("Expected ParsedClientResult::ProductResult") } diff --git a/src/client/result/ResultParser.rs b/src/client/result/ResultParser.rs index 829913e..402f37e 100644 --- a/src/client/result/ResultParser.rs +++ b/src/client/result/ResultParser.rs @@ -34,8 +34,9 @@ use urlencoding::decode; use crate::{exceptions::Exceptions, RXingResult}; use super::{ - GeoResultParser, ISBNResultParser, ParsedClientResult, ParsedRXingResult, SMSMMSResultParser, - TelResultParser, TextParsedRXingResult, WifiResultParser, ProductResultParser, + GeoResultParser, ISBNResultParser, ParsedClientResult, ParsedRXingResult, ProductResultParser, + SMSMMSResultParser, TelResultParser, TextParsedRXingResult, URIResultParser, URLTOResultParser, + WifiResultParser, }; /** @@ -102,7 +103,7 @@ pub fn getMassagedText(result: &RXingResult) -> String { } pub fn parseRXingResult(theRXingResult: &RXingResult) -> ParsedClientResult { - let PARSERS: [&ParserFunction; 6] = [ + let PARSERS: [&ParserFunction; 8] = [ // new BookmarkDoCoMoRXingResultParser(), // new AddressBookDoCoMoRXingResultParser(), // new EmailDoCoMoRXingResultParser(), @@ -117,8 +118,8 @@ pub fn parseRXingResult(theRXingResult: &RXingResult) -> ParsedClientResult { // new SMSTOMMSTORXingResultParser(), &GeoResultParser::parse, &WifiResultParser::parse, - // new URLTORXingResultParser(), - // new URIRXingResultParser(), + &URLTOResultParser::parse, + &URIResultParser::parse, &ISBNResultParser::parse, &ProductResultParser::parse, // new ExpandedProductRXingResultParser(), @@ -212,14 +213,14 @@ pub fn isStringOfDigits(value: &str, length: usize) -> bool { !value.is_empty() && length > 0 && length == value.len() && matcher.is_match(value) } -pub fn isSubstringOfDigits(value: &str, offset: i32, length: usize) -> bool { +pub fn isSubstringOfDigits(value: &str, offset: usize, length: usize) -> bool { if value.is_empty() || length <= 0 { return false; } let max = offset as usize + length; let matcher = Regex::new(DIGITS).unwrap(); - let sub_seq = &value[offset as usize..offset as usize + max]; + let sub_seq = &value[offset as usize..max]; value.len() >= max && matcher.is_match(sub_seq) } diff --git a/src/client/result/URIParsedResult.java b/src/client/result/URIParsedResult.java deleted file mode 100644 index bf1f0d4..0000000 --- a/src/client/result/URIParsedResult.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.client.result; - -/** - * A simple result type encapsulating a URI that has no further interpretation. - * - * @author Sean Owen - */ -public final class URIParsedRXingResult extends ParsedRXingResult { - - private final String uri; - private final String title; - - public URIParsedRXingResult(String uri, String title) { - super(ParsedRXingResultType.URI); - this.uri = massageURI(uri); - this.title = title; - } - - public String getURI() { - return uri; - } - - public String getTitle() { - return title; - } - - /** - * @return true if the URI contains suspicious patterns that may suggest it intends to - * mislead the user about its true nature - * @deprecated see {@link URIRXingResultParser#isPossiblyMaliciousURI(String)} - */ - @Deprecated - public boolean isPossiblyMaliciousURI() { - return URIRXingResultParser.isPossiblyMaliciousURI(uri); - } - - @Override - public String getDisplayRXingResult() { - StringBuilder result = new StringBuilder(30); - maybeAppend(title, result); - maybeAppend(uri, result); - return result.toString(); - } - - /** - * Transforms a string that represents a URI into something more proper, by adding or canonicalizing - * the protocol. - */ - private static String massageURI(String uri) { - uri = uri.trim(); - int protocolEnd = uri.indexOf(':'); - if (protocolEnd < 0 || isColonFollowedByPortNumber(uri, protocolEnd)) { - // No protocol, or found a colon, but it looks like it is after the host, so the protocol is still missing, - // so assume http - uri = "http://" + uri; - } - return uri; - } - - private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) { - int start = protocolEnd + 1; - int nextSlash = uri.indexOf('/', start); - if (nextSlash < 0) { - nextSlash = uri.length(); - } - return RXingResultParser.isSubstringOfDigits(uri, start, nextSlash - start); - } - - -} diff --git a/src/client/result/URIParsedResult.rs b/src/client/result/URIParsedResult.rs new file mode 100644 index 0000000..3befaa1 --- /dev/null +++ b/src/client/result/URIParsedResult.rs @@ -0,0 +1,102 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, V&ersion 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.client.result; + +use super::{ParsedRXingResult, ParsedRXingResultType, ResultParser, URIResultParser}; + +/** + * A simple result type encapsulating a URI that has no further interpretation. + * + * @author Sean Owen + */ +pub struct URIParsedRXingResult { + uri: String, + title: String, +} +impl ParsedRXingResult for URIParsedRXingResult { + fn getType(&self) -> super::ParsedRXingResultType { + ParsedRXingResultType::URI + } + + fn getDisplayRXingResult(&self) -> String { + let mut result = String::with_capacity(30); + ResultParser::maybe_append_string(&self.title, &mut result); + ResultParser::maybe_append_string(&self.uri, &mut result); + result + } +} +impl URIParsedRXingResult { + pub fn new(uri: String, title: String) -> Self { + Self { + uri: Self::massage_uri(&uri), + title + } + } + + pub fn getURI(&self) -> &str { + &self.uri + } + + pub fn getTitle(&self) -> &str { + &self.title + } + + /** + * @return true if the URI contains suspicious patterns that may suggest it intends to + * mislead the user about its true nature + * @deprecated see {@link URIRXingResultParser#isPossiblyMaliciousURI(String)} + */ + #[deprecated] + pub fn is_possibly_malicious_uri(&self) -> bool { + return URIResultParser::is_possibly_malicious_uri(&self.uri); + } + + /** + * Transforms a string that represents a URI into something more proper, by adding or canonicalizing + * the protocol. + */ + fn massage_uri(uri: &str) -> String { + let mut uri = String::from(uri.trim()); + // let protocolEnd = uri.find(':'); + if let Some(protocolEnd) = uri.find(':') { + if Self::is_colon_followed_by_port_number(&uri, protocolEnd) { + // No protocol, or found a colon, but it looks like it is after the host, so the protocol is still missing, + // so assume http + uri = format!("http://{}", &uri); + // uri = updated_uri.as_str() + } + }else { + uri = format!("http://{}", &uri); + } + + uri + } + + fn is_colon_followed_by_port_number(uri: &str, protocol_end: usize) -> bool { + let start = protocol_end + 1; + let nextSlash = if let Some(pos) = uri[start..].find('/') { + pos + start + } else { + uri.len() + }; + // let nextSlash = uri.indexOf('/', start); + // if (nextSlash < 0) { + // nextSlash = uri.length(); + // } + return ResultParser::isSubstringOfDigits(uri, start, nextSlash - start); + } +} diff --git a/src/client/result/URIParsedResultTestCase.java b/src/client/result/URIParsedResultTestCase.java deleted file mode 100644 index bbaf302..0000000 --- a/src/client/result/URIParsedResultTestCase.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.client.result; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.RXingResult; -import org.junit.Assert; -import org.junit.Test; - -/** - * Tests {@link URIParsedRXingResult}. - * - * @author Sean Owen - */ -public final class URIParsedRXingResultTestCase extends Assert { - - @Test - public void testBookmarkDocomo() { - doTest("MEBKM:URL:google.com;;", "http://google.com", null); - doTest("MEBKM:URL:http://google.com;;", "http://google.com", null); - doTest("MEBKM:URL:google.com;TITLE:Google;", "http://google.com", "Google"); - } - - @SuppressWarnings("checkstyle:lineLength") - @Test - public void testURI() { - doTest("google.com", "http://google.com", null); - doTest("123.com", "http://123.com", null); - doTest("http://google.com", "http://google.com", null); - doTest("https://google.com", "https://google.com", null); - doTest("google.com:443", "http://google.com:443", null); - doTest("https://www.google.com/calendar/hosted/google.com/embed?mode=AGENDA&force_login=true&src=google.com_726f6f6d5f6265707075@resource.calendar.google.com", - "https://www.google.com/calendar/hosted/google.com/embed?mode=AGENDA&force_login=true&src=google.com_726f6f6d5f6265707075@resource.calendar.google.com", - null); - doTest("otpauth://remoteaccess?devaddr=00%a1b2%c3d4&devname=foo&key=bar", - "otpauth://remoteaccess?devaddr=00%a1b2%c3d4&devname=foo&key=bar", - null); - doTest("s3://amazon.com:8123", "s3://amazon.com:8123", null); - doTest("HTTP://R.BEETAGG.COM/?12345", "HTTP://R.BEETAGG.COM/?12345", null); - } - - @Test - public void testNotURI() { - doTestNotUri("google.c"); - doTestNotUri(".com"); - doTestNotUri(":80/"); - doTestNotUri("ABC,20.3,AB,AD"); - doTestNotUri("http://google.com?q=foo bar"); - doTestNotUri("12756.501"); - doTestNotUri("google.50"); - doTestNotUri("foo.bar.bing.baz.foo.bar.bing.baz"); - } - - @Test - public void testURLTO() { - doTest("urlto::bar.com", "http://bar.com", null); - doTest("urlto::http://bar.com", "http://bar.com", null); - doTest("urlto:foo:bar.com", "http://bar.com", "foo"); - } - - @Test - public void testGarbage() { - doTestNotUri("Da65cV1g^>%^f0bAbPn1CJB6lV7ZY8hs0Sm:DXU0cd]GyEeWBz8]bUHLB"); - doTestNotUri("DEA\u0003\u0019M\u0006\u0000\bå\u0000‡HO\u0000X$\u0001\u0000\u001Fwfc\u0007!þ“˜" + - "\u0013\u0013¾Z{ùÎÝڗZ§¨+y_zbñk\u00117¸\u000E†Ü\u0000\u0000\u0000\u0000" + - "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" + - "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000£.ux"); - } - - @Test - public void testIsPossiblyMalicious() { - doTestIsPossiblyMalicious("http://google.com", false); - doTestIsPossiblyMalicious("http://google.com@evil.com", true); - doTestIsPossiblyMalicious("http://google.com:@evil.com", true); - doTestIsPossiblyMalicious("google.com:@evil.com", false); - doTestIsPossiblyMalicious("https://google.com:443", false); - doTestIsPossiblyMalicious("https://google.com:443/", false); - doTestIsPossiblyMalicious("https://evil@google.com:443", true); - doTestIsPossiblyMalicious("http://google.com/foo@bar", false); - doTestIsPossiblyMalicious("http://google.com/@@", false); - } - - @Test - public void testMaliciousUnicode() { - doTestIsPossiblyMalicious("https://google.com\u2215.evil.com/stuff", true); - doTestIsPossiblyMalicious("\u202ehttps://dylankatz.com/moc.elgoog.www//:sptth", true); - } - - @Test - public void testExotic() { - doTest("bitcoin:mySD89iqpmptrK3PhHFW9fa7BXiP7ANy3Y", "bitcoin:mySD89iqpmptrK3PhHFW9fa7BXiP7ANy3Y", null); - doTest("BTCTX:-TC4TO3$ZYZTC5NC83/SYOV+YGUGK:$BSF0P8/STNTKTKS.V84+JSA$LB+EHCG+8A725.2AZ-NAVX3VBV5K4MH7UL2.2M:" + - "F*M9HSL*$2P7T*FX.ZT80GWDRV0QZBPQ+O37WDCNZBRM3EQ0S9SZP+3BPYZG02U/LA*89C2U.V1TS.CT1VF3DIN*HN3W-O-" + - "0ZAKOAB32/.8:J501GJJTTWOA+5/6$MIYBERPZ41NJ6-WSG/*Z48ZH*LSAOEM*IXP81L:$F*W08Z60CR*C*P.JEEVI1F02J07L6+" + - "W4L1G$/IC*$16GK6A+:I1-:LJ:Z-P3NW6Z6ADFB-F2AKE$2DWN23GYCYEWX9S8L+LF$VXEKH7/R48E32PU+A:9H:8O5", - "BTCTX:-TC4TO3$ZYZTC5NC83/SYOV+YGUGK:$BSF0P8/STNTKTKS.V84+JSA$LB+EHCG+8A725.2AZ-NAVX3VBV5K4MH7UL2.2M:" + - "F*M9HSL*$2P7T*FX.ZT80GWDRV0QZBPQ+O37WDCNZBRM3EQ0S9SZP+3BPYZG02U/LA*89C2U.V1TS.CT1VF3DIN*HN3W-O-" + - "0ZAKOAB32/.8:J501GJJTTWOA+5/6$MIYBERPZ41NJ6-WSG/*Z48ZH*LSAOEM*IXP81L:$F*W08Z60CR*C*P.JEEVI1F02J07L6+" + - "W4L1G$/IC*$16GK6A+:I1-:LJ:Z-P3NW6Z6ADFB-F2AKE$2DWN23GYCYEWX9S8L+LF$VXEKH7/R48E32PU+A:9H:8O5", - null); - doTest("opc.tcp://test.samplehost.com:4841", "opc.tcp://test.samplehost.com:4841", null); - } - - private static void doTest(String contents, String uri, String title) { - RXingResult fakeRXingResult = new RXingResult(contents, null, null, BarcodeFormat.QR_CODE); - ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult); - assertSame(ParsedRXingResultType.URI, result.getType()); - URIParsedRXingResult uriRXingResult = (URIParsedRXingResult) result; - assertEquals(uri, uriRXingResult.getURI()); - assertEquals(title, uriRXingResult.getTitle()); - } - - private static void doTestNotUri(String text) { - RXingResult fakeRXingResult = new RXingResult(text, null, null, BarcodeFormat.QR_CODE); - ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult); - assertSame(ParsedRXingResultType.TEXT, result.getType()); - assertEquals(text, result.getDisplayRXingResult()); - } - - private static void doTestIsPossiblyMalicious(String uri, boolean malicious) { - RXingResult fakeRXingResult = new RXingResult(uri, null, null, BarcodeFormat.QR_CODE); - ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult); - assertSame(malicious ? ParsedRXingResultType.TEXT : ParsedRXingResultType.URI, result.getType()); - } - -} diff --git a/src/client/result/URIParsedResultTestCase.rs b/src/client/result/URIParsedResultTestCase.rs new file mode 100644 index 0000000..ee8aad5 --- /dev/null +++ b/src/client/result/URIParsedResultTestCase.rs @@ -0,0 +1,173 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.client.result; + +// import com.google.zxing.BarcodeFormat; +// import com.google.zxing.RXingResult; +// import org.junit.Assert; +// import org.junit.Test; + +/** + * Tests {@link URIParsedRXingResult}. + * + * @author Sean Owen + */ +// public final class URIParsedRXingResultTestCase extends Assert { +use crate::{ + client::result::{ParsedClientResult, ParsedRXingResult, ParsedRXingResultType, ResultParser}, + BarcodeFormat, RXingResult, +}; + +#[test] +fn test_bookmark_docomo() { + do_test("MEBKM:URL:google.com;;", "http://google.com", ""); + do_test("MEBKM:URL:http://google.com;;", "http://google.com", ""); + do_test( + "MEBKM:URL:google.com;TITLE:Google;", + "http://google.com", + "Google", + ); +} + +#[test] +fn test_uri() { + do_test("google.com", "http://google.com", ""); + do_test("123.com", "http://123.com", ""); + do_test("http://google.com", "http://google.com", ""); + do_test("https://google.com", "https://google.com", ""); + do_test("google.com:443", "http://google.com:443", ""); + do_test("https://www.google.com/calendar/hosted/google.com/embed?mode=AGENDA&force_login=true&src=google.com_726f6f6d5f6265707075@resource.calendar.google.com", + "https://www.google.com/calendar/hosted/google.com/embed?mode=AGENDA&force_login=true&src=google.com_726f6f6d5f6265707075@resource.calendar.google.com", + ""); + do_test( + "otpauth://remoteaccess?devaddr=00%a1b2%c3d4&devname=foo&key=bar", + "otpauth://remoteaccess?devaddr=00%a1b2%c3d4&devname=foo&key=bar", + "", + ); + do_test("s3://amazon.com:8123", "s3://amazon.com:8123", ""); + do_test( + "HTTP://R.BEETAGG.COM/?12345", + "HTTP://R.BEETAGG.COM/?12345", + "", + ); +} + +#[test] +fn test_not_uri() { + do_test_not_uri("google.c"); + do_test_not_uri(".com"); + do_test_not_uri(":80/"); + do_test_not_uri("ABC,20.3,AB,AD"); + do_test_not_uri("http://google.com?q=foo bar"); + do_test_not_uri("12756.501"); + do_test_not_uri("google.50"); + do_test_not_uri("foo.bar.bing.baz.foo.bar.bing.baz"); +} + +#[test] +fn test_urlto() { + do_test("urlto::bar.com", "http://bar.com", ""); + do_test("urlto::http://bar.com", "http://bar.com", ""); + do_test("urlto:foo:bar.com", "http://bar.com", "foo"); +} + +#[test] +fn test_garbage() { + do_test_not_uri("Da65cV1g^>%^f0bAbPn1CJB6lV7ZY8hs0Sm:DXU0cd]GyEeWBz8]bUHLB"); + // used \u{0008} as java \b + do_test_not_uri("DEA\u{0003}\u{0019}M\u{0006}\u{0000}\u{0008}å\u{0000}‡HO\u{0000}X$\u{0001}\u{0000}\u{001F}wfc\u{0007}!þ“˜\ + \u{0013}\u{0013}¾Z{ùÎÝڗZ§¨+y_zbñk\u{0011}7¸\u{000E}†Ü\u{0000}\u{0000}\u{0000}\u{0000}\ + \u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\ + \u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}\u{0000}£.ux"); +} + +#[test] +fn test_is_possibly_malicious() { + do_test_is_possibly_malicious("http://google.com", false); + do_test_is_possibly_malicious("http://google.com@evil.com", true); + do_test_is_possibly_malicious("http://google.com:@evil.com", true); + do_test_is_possibly_malicious("google.com:@evil.com", false); + do_test_is_possibly_malicious("https://google.com:443", false); + do_test_is_possibly_malicious("https://google.com:443/", false); + do_test_is_possibly_malicious("https://evil@google.com:443", true); + do_test_is_possibly_malicious("http://google.com/foo@bar", false); + do_test_is_possibly_malicious("http://google.com/@@", false); +} + +#[test] +fn test_malicious_unicode() { + do_test_is_possibly_malicious("https://google.com\u{2215}.evil.com/stuff", true); + do_test_is_possibly_malicious("\u{202e}https://dylankatz.com/moc.elgoog.www//:sptth", true); +} + +#[test] +fn test_exotic() { + do_test( + "bitcoin:mySD89iqpmptrK3PhHFW9fa7BXiP7ANy3Y", + "bitcoin:mySD89iqpmptrK3PhHFW9fa7BXiP7ANy3Y", + "", + ); + do_test("BTCTX:-TC4TO3$ZYZTC5NC83/SYOV+YGUGK:$BSF0P8/STNTKTKS.V84+JSA$LB+EHCG+8A725.2AZ-NAVX3VBV5K4MH7UL2.2M:/ + F*M9HSL*$2P7T*FX.ZT80GWDRV0QZBPQ+O37WDCNZBRM3EQ0S9SZP+3BPYZG02U/LA*89C2U.V1TS.CT1VF3DIN*HN3W-O-/ + 0ZAKOAB32/.8:J501GJJTTWOA+5/6$MIYBERPZ41NJ6-WSG/*Z48ZH*LSAOEM*IXP81L:$F*W08Z60CR*C*P.JEEVI1F02J07L6+/ + W4L1G$/IC*$16GK6A+:I1-:LJ:Z-P3NW6Z6ADFB-F2AKE$2DWN23GYCYEWX9S8L+LF$VXEKH7/R48E32PU+A:9H:8O5", + "BTCTX:-TC4TO3$ZYZTC5NC83/SYOV+YGUGK:$BSF0P8/STNTKTKS.V84+JSA$LB+EHCG+8A725.2AZ-NAVX3VBV5K4MH7UL2.2M:/ + F*M9HSL*$2P7T*FX.ZT80GWDRV0QZBPQ+O37WDCNZBRM3EQ0S9SZP+3BPYZG02U/LA*89C2U.V1TS.CT1VF3DIN*HN3W-O-/ + 0ZAKOAB32/.8:J501GJJTTWOA+5/6$MIYBERPZ41NJ6-WSG/*Z48ZH*LSAOEM*IXP81L:$F*W08Z60CR*C*P.JEEVI1F02J07L6+/ + W4L1G$/IC*$16GK6A+:I1-:LJ:Z-P3NW6Z6ADFB-F2AKE$2DWN23GYCYEWX9S8L+LF$VXEKH7/R48E32PU+A:9H:8O5", + ""); + do_test( + "opc.tcp://test.samplehost.com:4841", + "opc.tcp://test.samplehost.com:4841", + "", + ); +} + +fn do_test(contents: &str, uri: &str, title: &str) { + let fake_rxing_result = + RXingResult::new(contents, Vec::new(), Vec::new(), BarcodeFormat::QR_CODE); + let result = ResultParser::parseRXingResult(&fake_rxing_result); + assert_eq!(ParsedRXingResultType::URI, result.getType()); + if let ParsedClientResult::URIResult(uriRXingResult) = result { + assert_eq!(uri, uriRXingResult.getURI()); + assert_eq!(title, uriRXingResult.getTitle()); + } else { + panic!("Expected ParsedClientResult::URIResult(uriRXingResult)"); + } +} + +fn do_test_not_uri(text: &str) { + let fake_rxing_result = RXingResult::new(text, Vec::new(), Vec::new(), BarcodeFormat::QR_CODE); + let result = ResultParser::parseRXingResult(&fake_rxing_result); + assert_eq!(ParsedRXingResultType::TEXT, result.getType()); + assert_eq!(text, result.getDisplayRXingResult()); +} + +fn do_test_is_possibly_malicious(uri: &str, malicious: bool) { + let fake_rxing_result = RXingResult::new(uri, Vec::new(), Vec::new(), BarcodeFormat::QR_CODE); + let result = ResultParser::parseRXingResult(&fake_rxing_result); + assert_eq!( + if malicious { + ParsedRXingResultType::TEXT + } else { + ParsedRXingResultType::URI + }, + result.getType() + ); +} + +// } diff --git a/src/client/result/URIResultParser.java b/src/client/result/URIResultParser.java deleted file mode 100644 index 2527f01..0000000 --- a/src/client/result/URIResultParser.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.client.result; - -import com.google.zxing.RXingResult; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Tries to parse results that are a URI of some kind. - * - * @author Sean Owen - */ -public final class URIRXingResultParser extends RXingResultParser { - - private static final Pattern ALLOWED_URI_CHARS_PATTERN = - Pattern.compile("[-._~:/?#\\[\\]@!$&'()*+,;=%A-Za-z0-9]+"); - private static final Pattern USER_IN_HOST = Pattern.compile(":/*([^/@]+)@[^/]+"); - // See http://www.ietf.org/rfc/rfc2396.txt - private static final Pattern URL_WITH_PROTOCOL_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-.]+:"); - private static final Pattern URL_WITHOUT_PROTOCOL_PATTERN = Pattern.compile( - "([a-zA-Z0-9\\-]+\\.){1,6}[a-zA-Z]{2,}" + // host name elements; allow up to say 6 domain elements - "(:\\d{1,5})?" + // maybe port - "(/|\\?|$)"); // query, path or nothing - - @Override - public URIParsedRXingResult parse(RXingResult result) { - String rawText = getMassagedText(result); - // We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun - // Assume anything starting this way really means to be a URI - if (rawText.startsWith("URL:") || rawText.startsWith("URI:")) { - return new URIParsedRXingResult(rawText.substring(4).trim(), null); - } - rawText = rawText.trim(); - if (!isBasicallyValidURI(rawText) || isPossiblyMaliciousURI(rawText)) { - return null; - } - return new URIParsedRXingResult(rawText, null); - } - - /** - * @return true if the URI contains suspicious patterns that may suggest it intends to - * mislead the user about its true nature. At the moment this looks for the presence - * of user/password syntax in the host/authority portion of a URI which may be used - * in attempts to make the URI's host appear to be other than it is. Example: - * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear - * to connect to yourbank.com at first glance. - */ - static boolean isPossiblyMaliciousURI(String uri) { - return !ALLOWED_URI_CHARS_PATTERN.matcher(uri).matches() || USER_IN_HOST.matcher(uri).find(); - } - - static boolean isBasicallyValidURI(String uri) { - if (uri.contains(" ")) { - // Quick hack check for a common case - return false; - } - Matcher m = URL_WITH_PROTOCOL_PATTERN.matcher(uri); - if (m.find() && m.start() == 0) { // match at start only - return true; - } - m = URL_WITHOUT_PROTOCOL_PATTERN.matcher(uri); - return m.find() && m.start() == 0; - } - -} \ No newline at end of file diff --git a/src/client/result/URIResultParser.rs b/src/client/result/URIResultParser.rs new file mode 100644 index 0000000..0248bda --- /dev/null +++ b/src/client/result/URIResultParser.rs @@ -0,0 +1,118 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.client.result; + +// import com.google.zxing.RXingResult; + +// import java.util.regex.Matcher; +// import java.util.regex.Pattern; + +/** + * Tries to parse results that are a URI of some kind. + * + * @author Sean Owen + */ +// public final class URIRXingResultParser extends RXingResultParser { +use regex::Regex; + +use crate::RXingResult; + +use super::{ParsedClientResult, ResultParser, URIParsedRXingResult}; + +const ALLOWED_URI_CHARS_PATTERN: &'static str = "[-._~:/?#\\[\\]@!$&'()*+,;=%A-Za-z0-9]+"; +const USER_IN_HOST: &'static str = ":/*([^/@]+)@[^/]+"; +/// See http://www.ietf.org/rfc/rfc2396.txt +const URL_WITH_PROTOCOL_PATTERN: &'static str = "[a-zA-Z][a-zA-Z0-9+-.]+:"; +/// (host name elements; allow up to say 6 domain elements), (maybe port), (query, path or nothing) +const URL_WITHOUT_PROTOCOL_PATTERN: &'static str = + "([a-zA-Z0-9\\-]+\\.){1,6}[a-zA-Z]{2,}(:\\d{1,5})?(/|\\?|$)"; + +pub fn parse(result: &RXingResult) -> Option { + let raw_text = ResultParser::getMassagedText(result); + // We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun + // Assume anything starting this way really means to be a URI + if raw_text.starts_with("URL:") || raw_text.starts_with("URI:") { + return Some(ParsedClientResult::URIResult(URIParsedRXingResult::new( + raw_text[4..].trim().to_owned(), + "".to_owned(), + ))); + // return new URIParsedRXingResult(rawText.substring(4).trim(), null); + } + let raw_text = raw_text.trim(); + if !is_basically_valid_uri(raw_text) || is_possibly_malicious_uri(raw_text) { + return None; + } + Some(ParsedClientResult::URIResult(URIParsedRXingResult::new( + raw_text.to_owned(), + "".to_owned(), + ))) +} + +/** + * @return true if the URI contains suspicious patterns that may suggest it intends to + * mislead the user about its true nature. At the moment this looks for the presence + * of user/password syntax in the host/authority portion of a URI which may be used + * in attempts to make the URI's host appear to be other than it is. Example: + * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear + * to connect to yourbank.com at first glance. + */ +pub fn is_possibly_malicious_uri(uri: &str) -> bool { + let allowed_chars_regex = + Regex::new(ALLOWED_URI_CHARS_PATTERN).expect("Regex patterns should always copile"); + let user_in_host = Regex::new(USER_IN_HOST).expect("Regex patterns should always copile"); + + let allowed = if let Some(fnd) = allowed_chars_regex.find(uri){ + if fnd.start() == 0 && fnd.end() == uri.len() { + true + }else { + false + } + }else{ + false + }; + let user = user_in_host.is_match(uri); + + !allowed || user +} + +pub fn is_basically_valid_uri(uri: &str) -> bool { + if uri.contains(" ") { + // Quick hack check for a common case + return false; + } + let m = Regex::new(URL_WITH_PROTOCOL_PATTERN).expect("Regex patterns should always copile"); //.matcher(uri); + if let Some(found) = m.find(uri) { + if found.start() == 0 { + // match at start only + return true; + } + } + + let m = Regex::new(URL_WITHOUT_PROTOCOL_PATTERN).expect("Regex patterns should always copile"); //.matcher(uri); + if let Some(found) = m.find(uri) { + if found.start() == 0 { + // match at start only + true + } else { + false + } + } else { + false + } +} + +// } diff --git a/src/client/result/URLTOResultParser.java b/src/client/result/URLTOResultParser.rs similarity index 51% rename from src/client/result/URLTOResultParser.java rename to src/client/result/URLTOResultParser.rs index 1177b6b..f356d82 100644 --- a/src/client/result/URLTOResultParser.java +++ b/src/client/result/URLTOResultParser.rs @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.google.zxing.client.result; +// package com.google.zxing.client.result; -import com.google.zxing.RXingResult; +// import com.google.zxing.RXingResult; + +use crate::RXingResult; + +use super::{ParsedClientResult, ResultParser, URIParsedRXingResult}; /** * Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]". @@ -25,21 +29,27 @@ import com.google.zxing.RXingResult; * * @author Sean Owen */ -public final class URLTORXingResultParser extends RXingResultParser { - - @Override - public URIParsedRXingResult parse(RXingResult result) { - String rawText = getMassagedText(result); - if (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:")) { - return null; +pub fn parse(result: &RXingResult) -> Option { + let rawText = ResultParser::getMassagedText(result); + if !rawText.starts_with("urlto:") && !rawText.starts_with("URLTO:") { + return None; } - int titleEnd = rawText.indexOf(':', 6); - if (titleEnd < 0) { - return null; - } - String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd); - String uri = rawText.substring(titleEnd + 1); - return new URIParsedRXingResult(uri, title); - } + let titleEnd = if let Some(pos) = rawText[6..].find(':') { + pos + 6 + } else { + return None; + }; + let title = if titleEnd <= 6 { + "" + } else { + &rawText[6..titleEnd] + }; + let uri = &rawText[titleEnd + 1..]; -} \ No newline at end of file + Some(ParsedClientResult::URIResult(URIParsedRXingResult::new( + uri.to_owned(), + title.to_owned(), + ))) +} + +// } diff --git a/src/client/result/mod.rs b/src/client/result/mod.rs index 53b01b3..a9cff1c 100644 --- a/src/client/result/mod.rs +++ b/src/client/result/mod.rs @@ -14,6 +14,9 @@ mod SMSParsedResult; mod SMSMMSResultParser; mod ProductParsedResult; mod ProductResultParser; +mod URIParsedResult; +mod URIResultParser; +mod URLTOResultParser; use std::fmt; @@ -31,7 +34,7 @@ pub use GeoParsedResult::*; // pub use GeoResultParser::*; pub use SMSParsedResult::*; pub use ProductParsedResult::*; - +pub use URIParsedResult::*; #[cfg(test)] mod TelParsedResultTestCase; @@ -45,6 +48,8 @@ mod GeoParsedResultTestCase; mod SMSMMSParsedResultTestCase; #[cfg(test)] mod ProductParsedResultTestCase; +#[cfg(test)] +mod URIParsedResultTestCase; pub enum ParsedClientResult { TextResult(TextParsedRXingResult), @@ -54,6 +59,7 @@ pub enum ParsedClientResult { GeoResult(GeoParsedRXingResult), SMSResult(SMSParsedRXingResult), ProductResult(ProductParsedRXingResult), + URIResult(URIParsedRXingResult), } impl ParsedRXingResult for ParsedClientResult { @@ -66,6 +72,7 @@ impl ParsedRXingResult for ParsedClientResult { ParsedClientResult::GeoResult(a) => a.getType(), ParsedClientResult::SMSResult(a) => a.getType(), ParsedClientResult::ProductResult(a) => a.getType(), + ParsedClientResult::URIResult(a) => a.getType(), } @@ -80,6 +87,7 @@ impl ParsedRXingResult for ParsedClientResult { ParsedClientResult::GeoResult(a) => a.getDisplayRXingResult(), ParsedClientResult::SMSResult(a) => a.getDisplayRXingResult(), ParsedClientResult::ProductResult(a) => a.getDisplayRXingResult(), + ParsedClientResult::URIResult(a) => a.getDisplayRXingResult(),