A graphical client for plain-text protocols written in Rust with GTK. It currently supports the Gemini, Gopher and Finger protocols.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

link.rs 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. extern crate regex;
  2. use regex::Regex;
  3. use std::str::FromStr;
  4. use url::Url;
  5. #[derive(Debug)]
  6. pub enum Link {
  7. File(Url, String),
  8. Ftp(Url, String),
  9. Finger(Url, String),
  10. Gemini(Url, String),
  11. Gopher(Url, String),
  12. Http(Url, String),
  13. Image(Url, String),
  14. Relative(String, String),
  15. Unknown(Url, String),
  16. }
  17. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  18. pub struct ParseError;
  19. impl FromStr for Link {
  20. type Err = ParseError;
  21. // Parses a &str into an instance of 'Link'
  22. fn from_str(line: &str) -> Result<Self, Self::Err> {
  23. let mut els = line.split('\t');
  24. if line.starts_with('0') || line.starts_with('1') {
  25. let label = els.next().expect("no label");
  26. let path = els.next();
  27. let host = els.next();
  28. let port = els.next();
  29. if let Some(host) = host {
  30. if let Some(path) = path {
  31. let mut text = String::from(label);
  32. let selector = text.remove(0);
  33. if let Some(port) = port {
  34. if path != "" {
  35. match Url::parse(&format!(
  36. "gopher://{}:{}/{}{}",
  37. host, port, selector, path
  38. )) {
  39. Ok(url) => Ok(Link::Gopher(url, text)),
  40. Err(e) => {
  41. println!("ERR {:?}", e);
  42. Err(ParseError)
  43. }
  44. }
  45. } else {
  46. match Url::parse(&format!("gopher://{}:{}", host, port)) {
  47. Ok(url) => Ok(Link::Gopher(url, text)),
  48. Err(e) => {
  49. println!("ERR {:?}", e);
  50. Err(ParseError)
  51. }
  52. }
  53. }
  54. } else {
  55. Err(ParseError)
  56. }
  57. } else {
  58. Err(ParseError)
  59. }
  60. } else {
  61. Err(ParseError)
  62. }
  63. } else if line.starts_with('g') || line.starts_with('I') {
  64. let label = els.next().expect("no label");
  65. let path = els.next();
  66. let host = els.next();
  67. let port = els.next();
  68. if let Some(host) = host {
  69. if let Some(p) = path {
  70. let mut text = String::from(label);
  71. let selector = text.remove(0);
  72. let path = if p.starts_with('/') {
  73. p.to_string()
  74. } else {
  75. format!("/{}", p)
  76. };
  77. if let Some(port) = port {
  78. match Url::parse(&format!(
  79. "gopher://{}:{}/{}{}",
  80. host, port, selector, path
  81. )) {
  82. Ok(url) => Ok(Link::Image(url, text)),
  83. Err(e) => {
  84. println!("ERR {:?}", e);
  85. Err(ParseError)
  86. }
  87. }
  88. } else {
  89. Err(ParseError)
  90. }
  91. } else {
  92. Err(ParseError)
  93. }
  94. } else {
  95. Err(ParseError)
  96. }
  97. } else if line.starts_with('9') {
  98. let label = els.next().expect("no label");
  99. let path = els.next();
  100. let host = els.next();
  101. let port = els.next();
  102. if let Some(host) = host {
  103. if let Some(p) = path {
  104. let mut text = String::from(label);
  105. let selector = text.remove(0);
  106. let path = if p.starts_with('/') {
  107. p.to_string()
  108. } else {
  109. format!("/{}", p)
  110. };
  111. if let Some(port) = port {
  112. match Url::parse(&format!(
  113. "gopher://{}:{}/{}{}",
  114. host, port, selector, path
  115. )) {
  116. Ok(url) => Ok(Link::File(url, text)),
  117. Err(e) => {
  118. println!("ERR {:?}", e);
  119. Err(ParseError)
  120. }
  121. }
  122. } else {
  123. Err(ParseError)
  124. }
  125. } else {
  126. Err(ParseError)
  127. }
  128. } else {
  129. Err(ParseError)
  130. }
  131. } else if line.starts_with('[') {
  132. let mut url = String::from(line);
  133. let url = url.split_off(4);
  134. let label = String::from(line);
  135. match make_link(url, label) {
  136. Some(link) => Ok(link),
  137. None => Err(ParseError),
  138. }
  139. } else if line.starts_with('h') {
  140. let label = els.next();
  141. let url = els.next();
  142. if let Some(label) = label {
  143. if let Some(url) = url {
  144. let mut label = String::from(label);
  145. label.remove(0);
  146. let mut url = String::from(url);
  147. let url = url.split_off(4);
  148. match make_link(url, label) {
  149. Some(link) => Ok(link),
  150. None => Err(ParseError),
  151. }
  152. } else {
  153. Err(ParseError)
  154. }
  155. } else {
  156. Err(ParseError)
  157. }
  158. } else if line.contains("://") {
  159. let url = extract_url(line);
  160. let label = String::from(line);
  161. match make_link(String::from(url), label) {
  162. Some(link) => Ok(link),
  163. None => Err(ParseError),
  164. }
  165. } else {
  166. Err(ParseError)
  167. }
  168. }
  169. }
  170. pub fn make_link(url: String, label: String) -> Option<Link> {
  171. match Url::parse(&url) {
  172. Ok(url) => match url.scheme() {
  173. "finger" => Some(Link::Finger(url, label)),
  174. "ftp" => Some(Link::Ftp(url, label)),
  175. "gemini" => Some(Link::Gemini(url, label)),
  176. "gopher" => Some(Link::Gopher(url, label)),
  177. "http" => Some(Link::Http(url, label)),
  178. "https" => Some(Link::Http(url, label)),
  179. _ => Some(Link::Unknown(url, label)),
  180. },
  181. Err(url::ParseError::RelativeUrlWithoutBase) => Some(Link::Relative(url, label)),
  182. _ => None,
  183. }
  184. }
  185. const URL_REGEX: &str = r"((ftp|gopher|gemini|finger|http|https)://[a-zA-Z0-9-+&@#/%=~_|$?!:,.]*)";
  186. fn extract_url(line: &str) -> &str {
  187. let url_regexp = Regex::new(URL_REGEX).unwrap();
  188. if url_regexp.is_match(&line) {
  189. let caps = url_regexp.captures(line).unwrap();
  190. caps.get(1).map_or("", |m| m.as_str())
  191. } else {
  192. line
  193. }
  194. }