Giter Site home page Giter Site logo

mailparse's Introduction

mailparse

Build Status Crate

A simple parser for MIME email messages.

API

The primary entry point for this library is the following function:

    parse_mail(&[u8]) -> Result<ParsedMail, MailParseError>

This function takes the raw message data, including headers and body, and returns a structured object to more easily access pieces of the email message. There are other public functions that allow parsing smaller parts of the message as well; refer to the full documentation.

The library is designed to process real-world email data such as might be obtained by using the FETCH command on an IMAP server, or in a Maildir. As such, this library should successfully handle any valid MIME-formatted message, although it may not follow all the strict requirements in the various specifications that cover the format (predominantly IETF RFCs 822, 2045, 2047, 2822, and 5322). As an example, this library accepts raw message data which uses \n (ASCII LF) as line delimiters rather than the RFC-mandated \r\n (ASCII CRLF) line delimiters.

Example usage

    use mailparse::*;
    let parsed = parse_mail(concat!(
            "Subject: This is a test email\n",
            "Content-Type: multipart/alternative; boundary=foobar\n",
            "Date: Sun, 02 Oct 2016 07:06:22 -0700 (PDT)\n",
            "\n",
            "--foobar\n",
            "Content-Type: text/plain; charset=utf-8\n",
            "Content-Transfer-Encoding: quoted-printable\n",
            "\n",
            "This is the plaintext version, in utf-8. Proof by Euro: =E2=82=AC\n",
            "--foobar\n",
            "Content-Type: text/html\n",
            "Content-Transfer-Encoding: base64\n",
            "\n",
            "PGh0bWw+PGJvZHk+VGhpcyBpcyB0aGUgPGI+SFRNTDwvYj4gdmVyc2lvbiwgaW4g \n",
            "dXMtYXNjaWkuIFByb29mIGJ5IEV1cm86ICZldXJvOzwvYm9keT48L2h0bWw+Cg== \n",
            "--foobar--\n",
            "After the final boundary stuff gets ignored.\n").as_bytes())
        .unwrap();
    assert_eq!(parsed.headers.get_first_value("Subject"),
        Some("This is a test email".to_string()));
    assert_eq!(parsed.subparts.len(), 2);
    assert_eq!(parsed.subparts[0].get_body().unwrap(),
        "This is the plaintext version, in utf-8. Proof by Euro: \u{20AC}");
    assert_eq!(parsed.subparts[1].headers[1].get_value(), "base64");
    assert_eq!(parsed.subparts[1].ctype.mimetype, "text/html");
    assert!(parsed.subparts[1].get_body().unwrap().starts_with("<html>"));
    assert_eq!(dateparse(parsed.headers.get_first_value("Date").unwrap().as_str()).unwrap(), 1475417182);

Documentation

See the rustdoc at docs.rs.

MSRV policy

Currently the minimum supported Rust version (MSRV) is 1.51.0. MSRV increases will be kept to a minimum, and will always be accompanied with a minor version bump.

Support mailparse

If you want to support development of mailparse, please do so by donating your money, time, and/or energy to fighting climate change. A quick and easy way is to send a donation to Replant.ca Environmental, where every dollar gets a tree planted!

mailparse's People

Contributors

alexwennerberg avatar andir avatar aredridel avatar bruceg avatar djahandarie avatar dovahcrow avatar enckse avatar freaky avatar ftilde avatar gagath avatar hsivonen avatar inashivb avatar jelmer avatar link2xt avatar messense avatar nabijaczleweli avatar staktrace avatar tommilligan avatar tornaxo7 avatar ufoscout avatar vandenoever avatar wathiede avatar wookietreiber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mailparse's Issues

parse_mail crashes on properly crafted input

I've wrote simple fuzzer:

#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate mailparse;
use mailparse::*;

fuzz_target!(|data: &[u8]| {
    if let Ok(parsed) = parse_mail(data){
        if let Ok(Some(date)) = parsed.headers.get_first_value("Date") {
            let _ = dateparse(&date);
        }
    }
});

And literally after a few seconds of fuzzing I found crash.

Triggering code:

extern crate base64;
extern crate mailparse;
use mailparse::*;

const INPUT: &'static str = "U3ViamVjdDplcy1UeXBlOiBtdW50ZW50LVV5cGU6IW11bAAAAAAAAAAAamVjdDplcy1UeXBlOiBtdW50ZW50LVV5cGU6IG11bAAAAAAAAAAAAAAAAABTTUFZdWJqZf86OiP/dCBTdWJqZWN0Ol8KRGF0ZTog/////////////////////wAAAAAAAAAAAHQgYnJmAHQgYnJmZXItRW5jeXBlOnY9NmU3OjA2OgAAAAAAAAAAAAAAADEAAAAAAP/8mAAAAAAAAAAA+f///wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAPT0/PzEAAAEAAA==";

fn main() {
    if let Ok(parsed) = parse_mail(&base64::decode(INPUT).unwrap()){
        if let Ok(Some(date)) = parsed.headers.get_first_value("Date") {
            let _ = dateparse(&date);
        }
    }
}

Cargo.toml

[package]
name = "xxx-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false

[package.metadata]
cargo-fuzz = true

[dependencies]
lettre = "0.9"
lettre_email = "0.9"
mailparse = "0.8"

[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "fuzz_mailparse_parse"
path = "fuzz_targets/fuzz_mailparse_parse.rs"

...

Actual version is 0.8.0 in my cargo.lock file

Improperly decoded email message with `get_body` and `get_body_raw()

Hi @staktrace this is related to this issue. However, I'm trying to decode this email using this application

But it generates these weird characters in my email
weird email

Interesting both

let mail_content: String = parsed_mail.subparts[1].get_body().unwrap();

and

let mail_content: String = parsed_mail.subparts[1]
        .get_body_raw()
        .unwrap()
        .iter()
        .map(|&x| x as u8)
        .map(|x| (x) as char)
        .collect();

doesn't seem to work here; I don't know if there's something I'm doing wrong with this API.

dateparse as a separate crate?

The date parser in mailparse is very useful, much more so than the one in chrono, which can't read reasonable (but technically not standard complaint) dates, let alone realistic ones.

Would you be interested in maintaining it as a separate crate, so I can use it without depending on mailparse? Or, would you be happy with me forking your implementation to a new crate? Same license, or the Rust standard MIT OR Apache2?

Forced allocation in MailHeader

The implementation of MailHeader::get_key() is as follows:

pub fn get_key(&self) -> String {
    decode_latin1(self.key).into_owned()
}

And decode_latin1() returns a Cow<str>, which, for the vast majority of header names will be the Borrowed variant.

My dataset is 850`000ish mails with on-average-5 headers, and that sounds like an easy optimisation.

I'd submit a patch, but a two-line PR is more annoying to merge than it's worth (and I'm not sure what your breakage policy is), to change the funxion to

pub fn get_key(&self) -> Cow<str> {
    decode_latin1(self.key)
}

However, exposing the key and value members would be just as if not more useful, and it doesn't break any interface.

Failing to parse mail

I have this test, which unfortunately does not detect headers correctly

#[test]
fn test_parsing() {
        let plain = b"Chat-Disposition-Notification-To: [email protected]
    Chat-Group-ID: CovhGgau8M-
    Chat-Group-Name: Delta Chat Dev
    Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
     =?utf-8?Q?all=3A?= rust core master ...
    Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
    Content-Transfer-Encoding: quoted-printable

    sidenote for all: things are trick atm recomm=
    end not to try to run with desktop or ios unless you are ready to hunt bugs

    -- =20
    Sent with my Delta Chat Messenger: https://delta.chat";
        let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");

        println!(
            "{:?}",
            mail.headers
                .iter()
                .map(|h| (h.get_key(), h.get_value()))
                .collect::<Vec<_>>()
        );
        assert_eq!(mail.headers.len(), 6);
        assert_eq!(
            mail.get_body().unwrap(),
            "    sidenote for all: things are trick atm recomm=
    end not to try to run with desktop or ios unless you are ready to hunt bugs

    -- =20
    Sent with my Delta Chat Messenger: https://delta.chat"
        );
}

It fails with

[(Ok("Chat-Disposition-Notification-To"), Ok("[email protected] Chat-Group-ID: CovhGgau8M- Chat-Group-Name: Delta Chat Dev Subject: Chat: Delta Chat Dev: sidenote forall: rust core master ...Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\" Content-Transfer-Encoding: quoted-printable"))]
thread 'e2ee::tests::test_mailmime_parse' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `6`', src/e2ee.rs:405:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Likely the email is not 100% mime compliant, but unfortunately this is what I get the inbox sometimes.

Typed mimetype

I think it would be good to have the mimetype field in ParsedContentType properly typed (instead of a string). Often used types should be encoded as static variants of an enum and for everything else there can be a Custom(&str) variant.
I guess matching over content types of subparts might be a pretty common exercise and encoding this in the type system helps catching mistakes due to typos.

crash: thread main attempted to subtract with overflow...

Code:

use mailparse::{parse_mail, MailHeaderMap, dateparse};

//noinspection SpellCheckingInspection
const INPUT: &'static str = "U3ViamVjdDogVGhpcyBpcyBhIHRlc3QgZW1haWwKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvYWx0ZXJuYXRpdmU7IGJvdW5kYXJ5PczMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMZm9vYmFyCkRhdGU6IFN1biwgMDIgT2MKCi1TdWJqZWMtZm9vYmFydDo=";

fn main() {
    let data = base64::decode(INPUT).unwrap();
    if let Ok(parsed) = parse_mail(&data){
        if let Ok(Some(date)) = parsed.headers.get_first_value("Date") {
            let _ = dateparse(&date);
        }
    }
}

Tested against mailparse = "0.8.1"

Documentation: attachment example

The Python library imbox has a straightforward example, including accessing attachments.

How can I access the attachments with mailparse?
I don't grasp it with its current documentation.

Related: jonhoo/rust-imap#157

Would an umbrella module be required (using both rust-imap and mailparse) to resemble the functionality of imbox? Would this be a good starting point?

new release?

do you mind tagging a new release? this way we could use it better from core-deltachat.

How to get the mail body untouched

When the body is encoded in base64, the get_body_raw() method decodes it before it is the returned.
Is there a way to get the untouched body instead?
I need to forward to another system an attachment encoded in base64, at the moment the only way to achieve it is to call the get_body_raw() and then encode the result in base64; anyway, both the decode and the following encode actions are expensive and completely useless.

Parse envelope header separately

Do this crate expect the mail being parsed to have the "envelope header", as the email in mbox files do? If so, could it expose a new method of parsing it? For now, it parses the envelope as an ordinary header and so yields somewhat nonsensical results like this:

key == "From [email protected] Thu Aug 14 11"
value == "15:29 2003"

Allow recovery of raw headers

I'd love to be able to use this library to parse, modify headers, and send onward messages.

Would you be open to a PR that adds get_value_raw() -> &[u8] to MailHeader ? I think that's sufficient for my case.

`parse_content_disposition` should be fallible rather than default

When parsing many e-mail headers using parse_content_disposition, only a small fraction will be Content-Disposition. If the header is not Content-Disposition or malformed, a default ParsedContentDisposition with field disposition Inline will be constructed as a fallback. This turns out to be costly, certainly since ParsedContentDisposition's params field is a BTreeMap<String, String>, which will be needlessly constructed every time.

Can we instead make parse_content_disposition fallible and let API consumers use ParsedContentDisposition’s Default implementation, so that the current fallback can be used optionally when parse_content_disposition fails?

License

Hello,

It would be great to and a LICENSE file to make code re-use easier.

Thanks

msgidparse: assumes References are separated by whitespace, but it is optional

The current implementation splits ids within the References header on whitespace:

for id in ids.split_whitespace() {

However, RFC5322 states (emphasis mine):

The "References:" and "In-Reply-To:" fields each contain one or more unique message identifiers, optionally separated by CFWS [whitespace].

We're hitting this edge case in some emails pulled from an Exchange server.

I'd like to work on a solution to this initially, if that's okay.


Edit, for clarity, a failing test in 0.12.1 looks like:

        assert_eq!(
            msgidparse("<[email protected]><[email protected]>").unwrap(),
            MessageIdList(vec![
                "[email protected]".to_string(),
                "[email protected]".to_string(),
            ])
        );

Unexpected newline in header key

Out of ~60k of the emails I just parsed 2 give me an Unexpected newline in header key error.

Here is the contents of one of the messages;

Delivered-To: [email protected]
Received: by 10.220.167.194 with SMTP id r2cs51443vcy;
        Wed, 4 May 2011 09:12:05 -0700 (PDT)
Received: by 10.227.176.135 with SMTP id be7mr1394123wbb.0.1304525525196;
        Wed, 04 May 2011 09:12:05 -0700 (PDT)
Return-Path: <[email protected]>
Received: from linphone.org (linphone.org [91.121.196.132])
        by mx.google.com with ESMTPS id v18si1163679wbb.35.2011.05.04.09.12.03
        (version=TLSv1/SSLv3 cipher=OTHER);
        Wed, 04 May 2011 09:12:04 -0700 (PDT)
Received-SPF: pass (google.com: domain of [email protected] designates 91.121.196.132 as permitted sender) client-ip=91.121.196.132;
Authentication-Results: mx.google.com; spf=pass (google.com: domain of [email protected] designates 91.121.196.132 as permitted sender) [email protected]
Received: by linphone.org (Postfix, from userid 33)
	id 0B2D41FFF3; Wed,  4 May 2011 18:12:02 +0200 (CEST)
To: [email protected]
Subject: Start your sip.linphone.org service
Reply-to: No reply @ Linphone.org <[email protected]>
From: No reply @ Linphone.org <[email protected]>
X-Sender: <www.linphone.org>
X-Mailer: PHP
X-auth-smtp-user: No reply @ Linphone.org <[email protected]> 
X-abuse-contact: No reply @ Linphone.org <[email protected]> 
Date: Wed, 4 May 2011 18:12:02 +0200
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="_----------=_parties_de3c0f96a0b7e75e3122b92462fd869c"
Message-Id: <[email protected]>
Content-Length: 1161
X-TUID: L598s4TdSAFL

--_----------=_parties_de3c0f96a0b7e75e3122b92462fd869c
Content-Type: text/plain
charset="utf-8"
Content-Transfer-Encoding: 8bit

Hello,

activation pending for using your Linphone account.

Please use the link bellow to activate your account :


http://www.linphone.org/eng/linphone/register-a-linphone-account.html?action=confirm&confirmation_key=


Regards,
 
The Linphone team.


--_----------=_parties_de3c0f96a0b7e75e3122b92462fd869c
Content-Type: text/html; charset="utf-8"; Content-Transfer-Encoding: 8bit;

<html>
	<head>
	  <title>Start your sip.linphone.org service</title>
	</head>
	<body>
          <p>Hello,</p>
	  <p>activation pending for using your Linphone account.<br />
	  Please use the link bellow to activate your account :</p>
	  <p><a href="http://www.linphone.org/eng/linphone/register-a-linphone-account.html?action=confirm&confirmation_key=">http://www.linphone.org/eng/linphone/register-a-linphone-account.html?action=confirm&confirmation_key=</a></p>
	  <p>&nbsp;</p>
	  <p>Regards,<br /> 
	  The Linphone team.</p>
	</body>
	</html>
--_----------=_parties_de3c0f96a0b7e75e3122b92462fd869c--

Body contains newline separators

The body of subparts (and probably the main body for non-mulitpart mails) will contain the end marker of the email ( \r\n\r\n ), which , to my knowledge,should no be considered part of the body but just recognized as the "email is complete" marker.

Unexpected newline in header key

Hi!

I've got "Unexpected newline in header key" on several emails from my mailbox.
I did a little research and have found "\r\nContent-Type..." input to parse_header func.
There's a really dirty workaround, but it works:

pub fn parse_header(raw_data1: &[u8]) -> Result<(MailHeader, usize), MailParseError> {
    let mut index = 0;
    while raw_data1[index] == 13 || raw_data1[index] == 10 {
        index += 1;
    }

    let raw_data = &raw_data1[index..];

    let mut it = raw_data.iter();
// ...
                key: &raw_data[0..v],
                value: &raw_data[ix_value_start..ix_value_end],
            },
                ix + index))
        }

How to fix it correctly?

Cannot use parse_mail() to parse mails from File because of lifetimes

Doing

pathes.iter()
    .map(|path| {
        let mut buf;
        File.open(path).read_to_end(&mut buf);
        parse_mail(&buf)
    })

is not possible because the lifetime of the result of parse_mail() requires the buf to outlife the function.

Can we have an interface which does not require this, please? It is really hard to use this crate otherwise.

Debug and Clone traits for struct Header

Hello!

I have been trying to use the Headers struct of this crate as a part of another struct in my code (MimeDecode). I wish to print and clone MimeDecode but I get an error

 ^^^^^^^^^^^^^ `Headers<'_>` cannot be formatted using `{:?}` because it doesn't implement `Debug`

where

#[derive(Debug, Clone)]
pub struct MimeDecode<'a> {
    pub parsable_headers: bool,
    pub parsable_body: bool,
    pub ctnt_attachment: bool,
    pub headers: Option<Headers<'a>>,
}

I'm not very well experienced with Rust but I tried implementing the Debug trait for Headers and found out we cannot do that on entities imported from external crates.
My question is (if all my above deduction is correct), is there a reason we do not have Debug and Clone implemented for this struct? I just wish to learn and maybe restructure what I am writing if we do not have these traits for a reason.

Thank you very much for taking out time to read! :)

Question: Is it possible to parse a stream of data line by line?

Hi!

I am trying to use mailparse while writing a parser for SMTP protocol. So far, I have been buffering the entire e-mail up and sending it at once as an argument to parse_mail function. This is OK for many usual situations but is troublesome when the e-mail is wayyy too big. Is it possible to parse the email line by line as it is sent over?

Using addrparse_header with MailHeaderMap

MailHeaderMap trait provides a function get_first_value to find header and return its value. But value is a String, so addrparse_header can't be applied to it.

Maybe extend MailHeaderMap with a function that returns header itself, instead of the result of applying .get_value() to it?

Incorrect unwrap() guard in is_boundary()

mailparse::is_boundary has the following:

            if v >= line.len() {
                return true;
            }
            let c = line.chars().nth(v).unwrap();

str::len operates in bytes, while chars() operates in codepoints. The precondition may be suitable as an optimisation if overruns are common, but it does not protect from unwanted unwraps:

count != len: 44 != 53
���Ù�Ù�v�Þ=?ISO-2022-JP?B?GyRCRjAyaBsoQg==?=

53 byte str, but only 44 characters.

Suggested fix:

fn is_boundary(line: &str, ix: Option<usize>) -> bool {
    ix.map(|v|
        line.chars()
            .nth(v)
            .map_or(true, |c| c.is_whitespace() || c == '"' || c == '(' || c == ')' || c == '<' || c == '>')
    ).unwrap_or(true)
}

With this I can parse my >2 million emails without a single panic.

Should mailparse try to handle malformed cases?

I downloaded my emails using the imap crate and passed them all through mailparse.

I had two failures, both from yahoo, when they acquired delicious. In both cases, the empty line between the headers and body was missing.

Should this be handled by mailparse? I'm not sure how common this is. In my case, it was 2 out of 150k emails, from the same sender.

MailHeader::get_value is adding spaces where it should not

Hello,

The comment over the get_value function says that the parser should get rid of the extra whitespace introduced by MIME for multiline headers. However I think there is a problem in the implementation. Parsing around 2000 mails with this nice library made me find this problem when using multiline UTF-8 subject:

extern crate mailparse;

use mailparse::parse_header;


fn main() {
    let raw = b"Subject: =?utf-8?q?this_is_?=\n\t=?utf-8?q?a_test?=";
    let (parsed, _) = parse_header(raw).unwrap();

    let key = parsed.get_key().unwrap();
    let value = parsed.get_value().unwrap();

    println!("{}: {}", key, value);

    assert_eq!(key, "Subject");
    assert_eq!(value, "this is a test");
}

And the output is:

Subject: this is  a test
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"this is  a test"`,
 right: `"this is a test"`', src/main.rs:16:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

An extra whitespace is added, where it should not.

I can propose patch if you indicate me what to change. I am not very familiar with the RFC but I think this is a bug because a lot of mails I parse have this extra-whitespace problem in subjects.

Upgrade base64 for security reasons

Hi,

It seems this library uses a version of the library base64 which is vulnerable to security issues. Please refer to https://github.com/RustSec/advisory-db/blob/master/Advisories.toml#L53 .

patch commit : marshallpierce/rust-base64@24ead98
patched version : [">= 0.5.2"]
used version : base64 = "0.1.1"

We have found 3 modules using your module as a direct dependency :

  • maildir
  • fapt-pkg
  • buzz

This is done using crates.io data. Feel free to close this issue if it's not relevant.

Thanks

Allow get_body() for "binary" encoding

Currently, ParsedMail::get_body() returns an error if binary content encoding was used. There are some messages that I'm trying to parse that use binary encoding for text/plain, and I think it would make sense if get_body() would work for them too.

According to RFC1341, there is no actual distinction between 8bit and binary transfer encodings except the limitation on line length. The text seems to suggest that 8bit and binary should be used to indicate type of the payload, but there is no requirement to do so.

If this makes sense, I can work on a PR to fix this.

Thanks!

Address parsing support (RFC 5322 3.4)

It would be great if mailparse contained a function for parsing address specifications. (RFC 5322 3.4).

Something like:

fn parse_address(raw: &[u8]) -> Result<Vec<Address>, AddressParseError>;

struct Address {
   pub display_name: Option<String>,
   pub address: String,
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.