Initial commit, partially done

This commit is contained in:
Travis Burtrum 2018-08-07 23:23:15 -04:00
commit 67e03a481f
9 changed files with 1440 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea/
Cargo.lock
target/
upload/

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "pastebin"
version = "0.0.0"
publish = false
[dependencies]
rocket = "0.3.14"
rocket_codegen = "0.3.14"
rand = "0.4"
multipart = "0.15.2"
toml = "0.4.6"
adjective_adjective_animal = "0.1.0"
#tokio = "0.1.7"
#tokio-codec = "0.1.0"

23
readme.md Normal file
View File

@ -0,0 +1,23 @@
Rust Bucket
-----------
My ideal vision of a pastebin/image/file host, easy to use from the command line with any tools, and from a browser with and without javascript
Borrows ideas and inspiration from many different pastebins over the years:
* [ZeroBin](https://github.com/sebsauvage/ZeroBin) (client-side encryption)
* [chefi](https://github.com/colemickens/chefi) (paste with netcat)
* [Rocket.rs example pastebin](https://github.com/SergioBenitez/Rocket/tree/master/examples/pastebin/src) (framework)
* [dank-paste](https://github.com/wpbirney/dank-paste) (multipart/form-data support)
* [ix.io](http://ix.io/) (downloadable client)
Delete after 1/2 views, 5/10 minutes, 1 hour/day/week/month/year, forever, delete after not viewed within X
Don't store file type, only used for link, can use any suffix for link
Store when to delete (how many views + how long), store how many times viewed, store when uploaded (or file creation date?)
Maybe if we only support 'burn after reading' like zerobin we can use a special created_date to signify this and then never count views, or, another file, since that would interfere with 'delete after X time'
Crazy random thoughts:
* hash IDs, use un-hashed ID to encrypt paste

328
src/main.rs Normal file
View File

@ -0,0 +1,328 @@
#![feature(plugin, decl_macro, custom_derive)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate adjective_adjective_animal;
//extern crate tokio;
//extern crate tokio_codec;
extern crate multipart;
mod paste_id;
mod mpu;
#[cfg(test)] mod tests;
use std::io;
use std::fs::File;
use std::path::{Path, PathBuf};
use rocket::response::NamedFile;
use rocket::Data;
use rocket::response::content;
use paste_id::PasteID;
use mpu::MultipartUpload;
use rocket::request::LenientForm;
use std::fs;
//use tokio_codec::{Decoder, BytesCodec};
//use tokio::net::TcpListener;
//use tokio::prelude::*;
use std::thread;
use std::net::TcpListener;
use std::io::{Write,Read};
use std::time::Duration;
const HOST: &'static str = "http://localhost:8000";
const UPLOAD_MAX_SIZE: u64 = 8 * 1024 * 1024;
fn new_paste() -> (String, String) {
let id = PasteID::new();
let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = HOST, id = id);
(filename, url)
}
fn upload(paste: Data, key: Option<PasteID>) -> io::Result<String> {
let (filename, url) = new_paste();
paste.stream_to_file(Path::new(&filename))?;
Ok(url)
}
fn upload_string(paste: &str, key: Option<PasteID>) -> io::Result<String> {
let (filename, url) = new_paste();
fs::write(filename, paste).expect("Unable to write file");
Ok(url)
}
#[derive(FromForm)]
struct PasteForm {
content: String,
extension: String,
}
// todo: change /w to /, shouldn't conflict because of format, but it does currently
#[post("/w", format = "application/x-www-form-urlencoded", data = "<paste>")]
fn web_post(paste: LenientForm<PasteForm>) -> io::Result<String> {
upload_string(&paste.get().content, None)
}
// todo: change /w to /, shouldn't conflict because of format, but it does currently
#[post("/m", format = "multipart/form-data", data = "<paste>")]
fn mpu_post(paste: MultipartUpload) -> io::Result<String> {
let (filename, url) = new_paste();
paste.stream_to_file(Path::new(&filename))?;
Ok(url)
}
#[put("/", data = "<paste>")]
fn upload_put(paste: Data) -> io::Result<String> {
upload(paste, None)
}
#[post("/", data = "<paste>")]
fn upload_post(paste: Data) -> io::Result<String> {
upload(paste, None)
}
#[patch("/", data = "<paste>")]
fn upload_patch(paste: Data) -> io::Result<String> {
upload(paste, None)
}
#[put("/<key>", data = "<paste>")]
fn upload_put_key(paste: Data, key: PasteID) -> io::Result<String> {
upload(paste, Some(key))
}
#[post("/<key>", data = "<paste>")]
fn upload_post_key(paste: Data, key: PasteID) -> io::Result<String> {
upload(paste, Some(key))
}
#[patch("/<key>", data = "<paste>")]
fn upload_patch_key(paste: Data, key: PasteID) -> io::Result<String> {
upload(paste, Some(key))
}
#[get("/<id>")]
fn get(id: PasteID) -> Option<content::Plain<File>> {
let filename = format!("upload/{id}", id = id);
File::open(&filename).map(|f| content::Plain(f)).ok()
}
#[delete("/<id>/<key>")]
fn delete(id: PasteID, key: PasteID) -> Option<content::Plain<File>> {
let filename = format!("upload/{id}", id = id);
File::open(&filename).map(|f| content::Plain(f)).ok()
}
#[patch("/<id>/<key>")]
fn patch(id: PasteID, key: PasteID) -> Option<content::Plain<File>> {
let filename = format!("upload/{id}", id = id);
File::open(&filename).map(|f| content::Plain(f)).ok()
}
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("static/index.html")
}
#[get("/static/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![
index, files,
web_post,
mpu_post,
upload_post, upload_put, upload_patch,
upload_post_key, upload_put_key, upload_patch_key,
get, delete, patch
])
}
// adapted from io::copy
fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where R: Read, W: Write
{
let mut buf : [u8; 8192] = [0; 8192];
let mut written = 0;
loop {
let len = match reader.read(&mut buf) {
Ok(0) => return Ok(written),
Ok(len) => len,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
};
writer.write_all(&buf[..len])?;
written += len as u64;
if written > UPLOAD_MAX_SIZE {
return Err(std::io::Error::from(std::io::ErrorKind::InvalidData))
}
}
}
fn run_tcp(){
// Bind the server's socket
thread::spawn(|| {
let timeout = Some(Duration::new(5, 0));
let mut listener = TcpListener::bind("127.0.0.1:12345").unwrap();
loop {
match listener.accept() {
Ok((mut stream, addr)) => {
thread::spawn(move || {
let (filename, url) = new_paste();
let mut paste_file = File::create(&filename).expect("cannot create file?");
stream.set_read_timeout(timeout).expect("set read timeout failed?");
stream.set_write_timeout(timeout).expect("set write timeout failed?");
stream.write(&url.into_bytes()).expect("write failed?");
stream.flush();
copy(&mut stream, &mut paste_file);
//handle_request(stream, addr);
})
},
Err(e) => {
thread::spawn(move || {
println!("Connection failed: {:?}", e)
})
},
};
};
});
/*
// Bind the server's socket
let addr = "127.0.0.1:12345".parse().unwrap();
let tcp = TcpListener::bind(&addr).unwrap();
// Iterate incoming connections
let server = tcp.incoming().for_each(|tcp| {
let id = PasteID::new(ID_LENGTH);
let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = HOST, id = id);
// Split up the read and write halves
//let (reader, writer) = tcp.split();
// Copy the data back to the client
let conn = tokio::io::copy(reader, writer)
// print what happened
.map(|(n, _, _)| {
println!("wrote {} bytes", n)
})
// Handle any errors
.map_err(|err| {
println!("IO error {:?}", err)
});
let conn = tokio::io::write_all(writer, url)
.then(|res| {
println!("wrote message; success={:?}", res.is_ok());
Ok(())
});
let mut paste_file = File::create(&filename)?;
let conn = tokio::io::re(reader, vec!(0; 4096)).then(move |res| {
let result = match res {
Ok((_, buf, n)) => {
//info!(client_logger, "persisted"; "filepath" => filepath);
paste_file.write(&buf[0..n]).unwrap();
//info!(client_logger, "replied"; "message" => url);
tokio::io::write_all(writer, format!("{}\n", url).as_bytes()).wait().unwrap();
//info!(client_logger, "finished connection");
Ok(())
}
Err(e) => {
//error!(client_logger, "failed to read from client");
Err(e)
}
};
drop(result);
Ok(())
});
let mut paste_file = tokio::fs::file::File::create(&filename);
let conn = tokio::io::copy(reader, paste_file)
// print what happened
.map(|(n, _, _)| {
println!("wrote {} bytes", n)
})
// Handle any errors
.map_err(|err| {
println!("IO error {:?}", err)
});
let framed = BytesCodec::new().framed(tcp);
let (writer, reader) = framed.split();
let mut paste_file = File::create(&filename)?;
let conn = writer.write_buf(url)
.then(|res| {
println!("wrote message; success={:?}", res.is_ok());
Ok(())
});
let conn = reader
.for_each(move|bytes| {
println!("bytes: {:?}", bytes);
paste_file.write_all(&bytes).expect("cannot write to file?");
Ok(())
})
// After our copy operation is complete we just print out some helpful
// information.
.and_then(|()| {
println!("Socket received FIN packet and closed connection");
Ok(())
})
.or_else(|err| {
println!("Socket closed with error: {:?}", err);
// We have to return the error to catch it in the next ``.then` call
Err(err)
})
.then(|result| {
println!("Socket closed with result: {:?}", result);
Ok(())
});
// Spawn the future as a concurrent task
tokio::spawn(conn);
Ok(())
})
.map_err(|err| {
println!("server error {:?}", err);
});
// Start the runtime and spin up the server
tokio::run(server);
*/
}
fn main() {
run_tcp();
rocket().launch();
}

63
src/mpu.rs Normal file
View File

@ -0,0 +1,63 @@
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::io;
use std::io::{Read, Cursor};
use rocket::data::{self, FromData};
use rocket::Data;
use rocket::Outcome;
use rocket::request::Request;
use rocket::http::Status;
use multipart::server::Multipart;
pub struct MultipartUpload {
file: Vec<u8>,
}
impl MultipartUpload {
pub fn stream_to_file(&self, path: &Path) -> io::Result<()> {
let mut f = File::create(path)?;
f.write_all(&self.file)
}
}
fn get_multipart(request: &Request, data: Data) -> Option<Multipart<Cursor<Vec<u8>>>> {
// All of these errors should be reported
let ct = request.headers().get_one("Content-Type")?;
let idx = ct.find("boundary=")?;
let boundary = &ct[(idx + "boundary=".len())..];
let mut d = Vec::new();
data.stream_to(&mut d).ok()?;
Some(Multipart::with_body(Cursor::new(d), boundary))
}
impl FromData for MultipartUpload {
type Error = ();
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
let mut mp = match get_multipart(&request, data) {
Some(m) => m,
None => return Outcome::Failure((Status::raw(421), ())),
};
// Custom implementation parts
let mut file = None;
mp.foreach_entry(|mut entry| match entry.headers.name.as_str() {
"file" => {
panic!("filename: {:?}", entry.headers.filename);
let mut d = Vec::new();
entry.data.read_to_end(&mut d).expect("cant read");
file = Some(d);
}
other => panic!("No known key {}", other),
}).expect("Unable to iterate");
Outcome::Success(MultipartUpload {
file: file.expect("file not set"),
})
}
}

51
src/paste_id.rs Normal file
View File

@ -0,0 +1,51 @@
use std::fmt;
use std::borrow::Cow;
use rocket::request::FromParam;
use rocket::http::RawStr;
use adjective_adjective_animal::Generator;
/// A _probably_ unique paste ID.
pub struct PasteID<'a>(Cow<'a, str>);
impl<'a> PasteID<'a> {
/// Generate a _probably_ unique ID with `size` characters. For readability,
/// the characters used are from the sets [0-9], [A-Z], [a-z]. The
/// probability of a collision depends on the value of `size` and the number
/// of IDs generated thus far.
pub fn new() -> PasteID<'static> {
let mut generator = Generator::default();
PasteID(Cow::Owned(generator.next().unwrap()))
}
}
impl<'a> fmt::Display for PasteID<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Returns `true` if `id` is a valid paste ID and `false` otherwise.
fn valid_id(id: &str) -> bool {
id.len() > 3 &&
id.chars().all(|c| {
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
})
}
/// Returns an instance of `PasteID` if the path segment is a valid ID.
/// Otherwise returns the invalid ID as the `Err` value.
impl<'a> FromParam<'a> for PasteID<'a> {
type Error = &'a RawStr;
fn from_param(param: &'a RawStr) -> Result<PasteID<'a>, &'a RawStr> {
match valid_id(param) {
true => Ok(PasteID(Cow::Borrowed(param))),
false => Err(param)
}
}
}

70
src/tests.rs Normal file
View File

@ -0,0 +1,70 @@
use super::{rocket, index};
use rocket::local::Client;
use rocket::http::{Status, ContentType};
fn extract_id(from: &str) -> Option<String> {
from.rfind('/').map(|i| &from[(i + 1)..]).map(|s| s.trim_right().to_string())
}
#[test]
fn check_index() {
let client = Client::new(rocket()).unwrap();
// Ensure the index returns what we expect.
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
//assert_eq!(response.body_string(), Some(index().into()))
}
fn upload_paste(client: &Client, body: &str) -> String {
let mut response = client.post("/").body(body).dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
extract_id(&response.body_string().unwrap()).unwrap()
}
fn download_paste(client: &Client, id: &str) -> String {
let mut response = client.get(format!("/{}", id)).dispatch();
assert_eq!(response.status(), Status::Ok);
response.body_string().unwrap()
}
#[test]
fn pasting() {
let client = Client::new(rocket()).unwrap();
// Do a trivial upload, just to make sure it works.
let body_1 = "Hello, world!";
let id_1 = upload_paste(&client, body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
// Make sure we can keep getting that paste.
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_1), body_1);
// Upload some unicode.
let body_2 = "こんにちは";
let id_2 = upload_paste(&client, body_2);
assert_eq!(download_paste(&client, &id_2), body_2);
// Make sure we can get both pastes.
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
// Now a longer upload.
let body_3 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit
in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.";
let id_3 = upload_paste(&client, body_3);
assert_eq!(download_paste(&client, &id_3), body_3);
assert_eq!(download_paste(&client, &id_1), body_1);
assert_eq!(download_paste(&client, &id_2), body_2);
}

12
static/api.txt Normal file
View File

@ -0,0 +1,12 @@
USAGE
POST /
accepts raw data in the body of the request and responds with a URL of
a page containing the body's content
EXMAPLE: curl --data-binary @file.txt http://localhost:8000
GET /<id>
retrieves the content for the paste with id `<id>`

875
static/index.html Normal file
View File

@ -0,0 +1,875 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rust Bucket</title>
<style type="text/css" media="screen">
html, body, .container, form { padding: 0; height: 100%; }
.container { margin: 10px; }
.error {
border: 2px solid red;
margin-bottom: 10px;
}
textarea {
width: 100%;
height: 75%;
padding: 10px;
box-sizing: border-box;
margin-bottom: 10px;
}
input[type="submit"] {
margin-top: 10px;
padding: 5px;
}
</style>
</head>
<body>
<div class="container">
<form action="/m"
enctype="multipart/form-data" method="post">
<p>
Select a file:<br>
<input type="file" name="file" size="40">
</p>
<div>
<input type="submit" value="Send">
</div>
</form>
<form method="post" action="/w">
<textarea name="content" rows="30" maxlength="393216" \
placeholder="Paste your code here..." cols="80"></textarea>
<br />
<!--
-->
<label for="extension">File Extension:</label>
<select name="extension">
<option value="Appfile">
Appfile
</option>
<option value="Berksfile">
Berksfile
</option>
<option value="Brewfile">
Brewfile
</option>
<option value="C">
C
</option>
<option value="Cheffile">
Cheffile
</option>
<option value="DOT">
DOT
</option>
<option value="Deliverfile">
Deliverfile
</option>
<option value="Emakefile">
Emakefile
</option>
<option value="Fastfile">
Fastfile
</option>
<option value="GNUmakefile">
GNUmakefile
</option>
<option value="Gemfile">
Gemfile
</option>
<option value="Guardfile">
Guardfile
</option>
<option value="M">
M
</option>
<option value="Makefile">
Makefile
</option>
<option value="OCamlMakefile">
OCamlMakefile
</option>
<option value="PL">
PL
</option>
<option value="R">
R
</option>
<option value="Rakefile">
Rakefile
</option>
<option value="Rantfile">
Rantfile
</option>
<option value="Rprofile">
Rprofile
</option>
<option value="S">
S
</option>
<option value="SConscript">
SConscript
</option>
<option value="SConstruct">
SConstruct
</option>
<option value="Scanfile">
Scanfile
</option>
<option value="Sconstruct">
Sconstruct
</option>
<option value="Snakefile">
Snakefile
</option>
<option value="Snapfile">
Snapfile
</option>
<option value="Thorfile">
Thorfile
</option>
<option value="Vagrantfile">
Vagrantfile
</option>
<option value="adp">
adp
</option>
<option value="applescript">
applescript
</option>
<option value="as">
as
</option>
<option value="asa">
asa
</option>
<option value="asp">
asp
</option>
<option value="babel">
babel
</option>
<option value="bash">
bash
</option>
<option value="bat">
bat
</option>
<option value="bib">
bib
</option>
<option value="bsh">
bsh
</option>
<option value="build">
build
</option>
<option value="builder">
builder
</option>
<option value="c">
c
</option>
<option value="c++">
c++
</option>
<option value="capfile">
capfile
</option>
<option value="cc">
cc
</option>
<option value="cgi">
cgi
</option>
<option value="cl">
cl
</option>
<option value="clj">
clj
</option>
<option value="cls">
cls
</option>
<option value="cmd">
cmd
</option>
<option value="config.ru">
config.ru
</option>
<option value="cp">
cp
</option>
<option value="cpp">
cpp
</option>
<option value="cpy">
cpy
</option>
<option value="cs">
cs
</option>
<option value="css">
css
</option>
<option value="css.erb">
css.erb
</option>
<option value="css.liquid">
css.liquid
</option>
<option value="csx">
csx
</option>
<option value="cxx">
cxx
</option>
<option value="d">
d
</option>
<option value="ddl">
ddl
</option>
<option value="di">
di
</option>
<option value="diff">
diff
</option>
<option value="dml">
dml
</option>
<option value="dot">
dot
</option>
<option value="dpr">
dpr
</option>
<option value="dtml">
dtml
</option>
<option value="el">
el
</option>
<option value="emakefile">
emakefile
</option>
<option value="erb">
erb
</option>
<option value="erbsql">
erbsql
</option>
<option value="erl">
erl
</option>
<option value="es6">
es6
</option>
<option value="fasl">
fasl
</option>
<option value="fcgi">
fcgi
</option>
<option value="gemspec">
gemspec
</option>
<option value="go">
go
</option>
<option value="gradle">
gradle
</option>
<option value="groovy">
groovy
</option>
<option value="gvy">
gvy
</option>
<option value="gyp">
gyp
</option>
<option value="gypi">
gypi
</option>
<option value="h">
h
</option>
<option value="h">
h
</option>
<option value="h">
h
</option>
<option value="h">
h
</option>
<option value="h++">
h++
</option>
<option value="haml">
haml
</option>
<option value="hh">
hh
</option>
<option value="hpp">
hpp
</option>
<option value="hrl">
hrl
</option>
<option value="hs">
hs
</option>
<option value="htm">
htm
</option>
<option value="html">
html
</option>
<option value="html.erb">
html.erb
</option>
<option value="hxx">
hxx
</option>
<option value="inc">
inc
</option>
<option value="inl">
inl
</option>
<option value="ipp">
ipp
</option>
<option value="irbrc">
irbrc
</option>
<option value="java">
java
</option>
<option value="jbuilder">
jbuilder
</option>
<option value="js">
js
</option>
<option value="js.erb">
js.erb
</option>
<option value="json">
json
</option>
<option value="jsp">
jsp
</option>
<option value="jsx">
jsx
</option>
<option value="l">
l
</option>
<option value="lhs">
lhs
</option>
<option value="lisp">
lisp
</option>
<option value="lsp">
lsp
</option>
<option value="ltx">
ltx
</option>
<option value="lua">
lua
</option>
<option value="m">
m
</option>
<option value="mak">
mak
</option>
<option value="make">
make
</option>
<option value="makefile">
makefile
</option>
<option value="markdn">
markdn
</option>
<option value="markdown">
markdown
</option>
<option value="matlab">
matlab
</option>
<option value="md">
md
</option>
<option value="mdown">
mdown
</option>
<option value="mk">
mk
</option>
<option value="ml">
ml
</option>
<option value="mli">
mli
</option>
<option value="mll">
mll
</option>
<option value="mly">
mly
</option>
<option value="mm">
mm
</option>
<option value="mud">
mud
</option>
<option value="opml">
opml
</option>
<option value="p">
p
</option>
<option value="pas">
pas
</option>
<option value="patch">
patch
</option>
<option value="php">
php
</option>
<option value="php3">
php3
</option>
<option value="php4">
php4
</option>
<option value="php5">
php5
</option>
<option value="php7">
php7
</option>
<option value="phps">
phps
</option>
<option value="phpt">
phpt
</option>
<option value="phtml">
phtml
</option>
<option value="pl">
pl
</option>
<option value="pm">
pm
</option>
<option value="pod">
pod
</option>
<option value="podspec">
podspec
</option>
<option value="prawn">
prawn
</option>
<option value="properties">
properties
</option>
<option value="py">
py
</option>
<option value="py3">
py3
</option>
<option value="pyi">
pyi
</option>
<option value="pyw">
pyw
</option>
<option value="r">
r
</option>
<option value="rabl">
rabl
</option>
<option value="rails">
rails
</option>
<option value="rake">
rake
</option>
<option value="rb">
rb
</option>
<option value="rbx">
rbx
</option>
<option value="rd">
rd
</option>
<option value="re">
re
</option>
<option value="rest">
rest
</option>
<option value="rhtml">
rhtml
</option>
<option value="rjs">
rjs
</option>
<option value="rpy">
rpy
</option>
<option value="rs">
rs
</option>
<option value="rss">
rss
</option>
<option value="rst">
rst
</option>
<option value="ruby.rail">
ruby.rail
</option>
<option value="rxml">
rxml
</option>
<option value="s">
s
</option>
<option value="sass">
sass
</option>
<option value="sbt">
sbt
</option>
<option value="scala">
scala
</option>
<option value="scm">
scm
</option>
<option value="sconstruct">
sconstruct
</option>
<option value="sh">
sh
</option>
<option value="shtml">
shtml
</option>
<option value="simplecov">
simplecov
</option>
<option value="sql">
sql
</option>
<option value="sql.erb">
sql.erb
</option>
<option value="ss">
ss
</option>
<option value="sty">
sty
</option>
<option value="svg">
svg
</option>
<option value="swift">
swift
</option>
<option value="t">
t
</option>
<option value="tcl">
tcl
</option>
<option value="tex">
tex
</option>
<option value="textile">
textile
</option>
<option value="thor">
thor
</option>
<option value="tld">
tld
</option>
<option value="tmpl">
tmpl
</option>
<option value="tpl">
tpl
</option>
<option value="ts">
ts
</option>
<option value="tsx">
tsx
</option>
<option selected value="txt">
txt
</option>
<option value="wscript">
wscript
</option>
<option value="xhtml">
xhtml
</option>
<option value="xml">
xml
</option>
<option value="xsd">
xsd
</option>
<option value="xslt">
xslt
</option>
<option value="yaml">
yaml
</option>
<option value="yaws">
yaws
</option>
<option value="yml">
yml
</option>
<option value="zsh">
zsh
</option>
</select>
<br />
<input type="submit" value="Paste!" />
</form>
</div>
</body>
</html>