sisimai 4.25.16-java → 5.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +3 -3
- data/ANALYTICAL-PRECISION +2 -2
- data/Benchmarks.mk +3 -3
- data/CONTRIBUTING +1 -1
- data/ChangeLog.md +412 -393
- data/Developers.mk +5 -6
- data/Gemfile +1 -1
- data/Makefile +15 -15
- data/README-JA.md +140 -78
- data/README.md +290 -143
- data/Rakefile +9 -3
- data/Repository.mk +2 -3
- data/lib/sisimai/address.rb +118 -74
- data/lib/sisimai/arf.rb +84 -82
- data/lib/sisimai/datetime.rb +5 -52
- data/lib/sisimai/{data → fact}/json.rb +7 -9
- data/lib/sisimai/fact/yaml.rb +31 -0
- data/lib/sisimai/fact.rb +468 -0
- data/lib/sisimai/lhost/activehunter.rb +12 -14
- data/lib/sisimai/lhost/amavis.rb +11 -14
- data/lib/sisimai/lhost/amazonses.rb +37 -41
- data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
- data/lib/sisimai/lhost/aol.rb +12 -14
- data/lib/sisimai/lhost/apachejames.rb +19 -21
- data/lib/sisimai/lhost/barracuda.rb +10 -12
- data/lib/sisimai/lhost/bigfoot.rb +21 -21
- data/lib/sisimai/lhost/biglobe.rb +15 -16
- data/lib/sisimai/lhost/courier.rb +20 -20
- data/lib/sisimai/lhost/domino.rb +23 -19
- data/lib/sisimai/lhost/einsundeins.rb +23 -18
- data/lib/sisimai/lhost/exchange2003.rb +30 -29
- data/lib/sisimai/lhost/exchange2007.rb +70 -58
- data/lib/sisimai/lhost/exim.rb +175 -161
- data/lib/sisimai/lhost/ezweb.rb +31 -56
- data/lib/sisimai/lhost/facebook.rb +21 -33
- data/lib/sisimai/lhost/fml.rb +43 -48
- data/lib/sisimai/lhost/gmail.rb +29 -29
- data/lib/sisimai/lhost/gmx.rb +18 -17
- data/lib/sisimai/lhost/googlegroups.rb +9 -10
- data/lib/sisimai/lhost/gsuite.rb +21 -27
- data/lib/sisimai/lhost/imailserver.rb +25 -39
- data/lib/sisimai/lhost/interscanmss.rb +28 -31
- data/lib/sisimai/lhost/kddi.rb +22 -28
- data/lib/sisimai/lhost/mailfoundry.rb +11 -12
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
- data/lib/sisimai/lhost/mailru.rb +33 -27
- data/lib/sisimai/lhost/mcafee.rb +21 -31
- data/lib/sisimai/lhost/messagelabs.rb +17 -20
- data/lib/sisimai/lhost/messagingserver.rb +40 -37
- data/lib/sisimai/lhost/mfilter.rb +15 -16
- data/lib/sisimai/lhost/mxlogic.rb +24 -23
- data/lib/sisimai/lhost/notes.rb +17 -17
- data/lib/sisimai/lhost/office365.rb +63 -27
- data/lib/sisimai/lhost/opensmtpd.rb +12 -13
- data/lib/sisimai/lhost/outlook.rb +12 -15
- data/lib/sisimai/lhost/postfix.rb +179 -129
- data/lib/sisimai/lhost/powermta.rb +12 -14
- data/lib/sisimai/lhost/qmail.rb +44 -47
- data/lib/sisimai/lhost/receivingses.rb +15 -20
- data/lib/sisimai/lhost/sendgrid.rb +34 -32
- data/lib/sisimai/lhost/sendmail.rb +66 -53
- data/lib/sisimai/lhost/surfcontrol.rb +19 -19
- data/lib/sisimai/lhost/v5sendmail.rb +45 -39
- data/lib/sisimai/lhost/verizon.rb +35 -39
- data/lib/sisimai/lhost/x1.rb +18 -17
- data/lib/sisimai/lhost/x2.rb +17 -14
- data/lib/sisimai/lhost/x3.rb +19 -19
- data/lib/sisimai/lhost/x4.rb +72 -57
- data/lib/sisimai/lhost/x5.rb +17 -19
- data/lib/sisimai/lhost/x6.rb +41 -17
- data/lib/sisimai/lhost/yahoo.rb +17 -16
- data/lib/sisimai/lhost/yandex.rb +16 -20
- data/lib/sisimai/lhost/zoho.rb +16 -15
- data/lib/sisimai/lhost.rb +8 -10
- data/lib/sisimai/mail/maildir.rb +1 -3
- data/lib/sisimai/mail/mbox.rb +3 -4
- data/lib/sisimai/mail/memory.rb +0 -1
- data/lib/sisimai/mail/stdin.rb +1 -3
- data/lib/sisimai/mail.rb +3 -7
- data/lib/sisimai/mda.rb +28 -42
- data/lib/sisimai/message.rb +435 -326
- data/lib/sisimai/order.rb +5 -5
- data/lib/sisimai/reason/authfailure.rb +64 -0
- data/lib/sisimai/reason/badreputation.rb +53 -0
- data/lib/sisimai/reason/blocked.rb +94 -160
- data/lib/sisimai/reason/contenterror.rb +8 -9
- data/lib/sisimai/reason/delivered.rb +4 -6
- data/lib/sisimai/reason/exceedlimit.rb +10 -12
- data/lib/sisimai/reason/expired.rb +6 -8
- data/lib/sisimai/reason/feedback.rb +2 -3
- data/lib/sisimai/reason/filtered.rb +17 -19
- data/lib/sisimai/reason/hasmoved.rb +9 -10
- data/lib/sisimai/reason/hostunknown.rb +15 -15
- data/lib/sisimai/reason/mailboxfull.rb +10 -12
- data/lib/sisimai/reason/mailererror.rb +18 -20
- data/lib/sisimai/reason/mesgtoobig.rb +9 -11
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +8 -11
- data/lib/sisimai/reason/notaccept.rb +13 -14
- data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +14 -12
- data/lib/sisimai/reason/rejected.rb +26 -24
- data/lib/sisimai/reason/requireptr.rb +69 -0
- data/lib/sisimai/reason/securityerror.rb +33 -36
- data/lib/sisimai/reason/spamdetected.rb +114 -147
- data/lib/sisimai/reason/speeding.rb +49 -0
- data/lib/sisimai/reason/suspend.rb +11 -11
- data/lib/sisimai/reason/syntaxerror.rb +11 -10
- data/lib/sisimai/reason/systemerror.rb +7 -9
- data/lib/sisimai/reason/systemfull.rb +7 -8
- data/lib/sisimai/reason/toomanyconn.rb +9 -11
- data/lib/sisimai/reason/undefined.rb +2 -3
- data/lib/sisimai/reason/userunknown.rb +129 -146
- data/lib/sisimai/reason/vacation.rb +3 -4
- data/lib/sisimai/reason/virusdetected.rb +10 -11
- data/lib/sisimai/reason.rb +59 -64
- data/lib/sisimai/rfc1894.rb +55 -28
- data/lib/sisimai/rfc2045.rb +373 -0
- data/lib/sisimai/rfc3464.rb +250 -308
- data/lib/sisimai/rfc3834.rb +42 -45
- data/lib/sisimai/rfc5322.rb +75 -100
- data/lib/sisimai/rfc5965.rb +31 -0
- data/lib/sisimai/rhost/cox.rb +5 -6
- data/lib/sisimai/rhost/franceptt.rb +6 -8
- data/lib/sisimai/rhost/godaddy.rb +12 -12
- data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
- data/lib/sisimai/rhost/iua.rb +9 -10
- data/lib/sisimai/rhost/kddi.rb +6 -8
- data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
- data/lib/sisimai/rhost/mimecast.rb +42 -40
- data/lib/sisimai/rhost/nttdocomo.rb +12 -12
- data/lib/sisimai/rhost/spectrum.rb +10 -12
- data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
- data/lib/sisimai/rhost.rb +23 -31
- data/lib/sisimai/smtp/command.rb +59 -0
- data/lib/sisimai/smtp/error.rb +4 -7
- data/lib/sisimai/smtp/reply.rb +161 -74
- data/lib/sisimai/smtp/status.rb +504 -393
- data/lib/sisimai/smtp/transcript.rb +124 -0
- data/lib/sisimai/smtp.rb +0 -1
- data/lib/sisimai/string.rb +74 -5
- data/lib/sisimai/time.rb +1 -2
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +35 -21
- data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
- data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
- data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
- data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
- data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
- data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
- data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
- data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
- data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
- data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
- data/sisimai-java.gemspec +1 -1
- data/sisimai.gemspec +1 -1
- metadata +42 -22
- data/.rspec +0 -2
- data/lib/sisimai/data/yaml.rb +0 -33
- data/lib/sisimai/data.rb +0 -411
- data/lib/sisimai/mime.rb +0 -456
- /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
- /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
data/README.md
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|

|
|
2
|
-
|
|
3
2
|
[](https://github.com/sisimai/rb-sisimai/blob/master/LICENSE)
|
|
4
3
|
[](https://coveralls.io/r/sisimai/rb-sisimai)
|
|
5
|
-
[](https://www.codacy.com/app/azumakuniyuki/rb-sisimai)
|
|
7
|
-
[](https://www.ruby-lang.org/)
|
|
4
|
+
[](https://www.ruby-lang.org/)
|
|
8
5
|
[](https://badge.fury.io/rb/sisimai)
|
|
9
6
|
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> **The default branch of this repository is [5-stable](https://github.com/sisimai/rb-sisimai/tree/5-stable)
|
|
9
|
+
> (Sisimai 5) since 2nd February 2024.**
|
|
10
|
+
> If you want to clone the old version, see the [4-stable](https://github.com/sisimai/rb-sisimai/tree/4-stable)[^1]
|
|
11
|
+
> branch instead. We have moved away from using both the `main` and `master` branches in our development process.
|
|
12
|
+
[^1]: Specify `-b 4-stable` when you clone Sisimai 4 for example, `git clone -b 4-stable https://github.com/sisimai/rb-sisimai.git`
|
|
13
|
+
|
|
14
|
+
> [!WARNING]
|
|
15
|
+
> Sisimai 5 requires Ruby 2.4 or later. Check the version of Ruby in your system before installing/upgrading
|
|
16
|
+
> by `ruby -v` command.
|
|
17
|
+
|
|
18
|
+
> [!CAUTION]
|
|
19
|
+
> [Sisimai 5](https://github.com/sisimai/rb-sisimai/releases/tag/v5.0.0) has not been uploaded to
|
|
20
|
+
> [RubyGems.org](https://rubygems.org/gems/sisimai) yet as of February 2nd. It will be available on
|
|
21
|
+
> RubyGems.org within a few months, but until then, please clone it from this repository.
|
|
22
|
+
|
|
10
23
|
- [**README-JA(日本語)**](README-JA.md)
|
|
11
24
|
- [What is Sisimai](#what-is-sisimai)
|
|
12
|
-
- [
|
|
25
|
+
- [The key features of sisimai](#the-key-features-of-sisimai)
|
|
13
26
|
- [Command line demo](#command-line-demo)
|
|
14
27
|
- [Setting Up Sisimai](#setting-up-sisimai)
|
|
15
28
|
- [System requirements](#system-requirements)
|
|
@@ -22,12 +35,14 @@
|
|
|
22
35
|
- [Callback feature](#callback-feature)
|
|
23
36
|
- [One-Liner](#one-liner)
|
|
24
37
|
- [Output example](#output-example)
|
|
25
|
-
- [Sisimai
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
38
|
+
- [Differences between Sisimai 4 and Sisimai 5](#differences-between-sisimai-4-and-sisimai-5)
|
|
39
|
+
- [Features](#features)
|
|
40
|
+
- [Decoding Methods](#decoding-methods)
|
|
41
|
+
- [MTA/ESP Module Names](#mtaesp-module-names)
|
|
42
|
+
- [Bounce Reasons](#bounce-reasons)
|
|
28
43
|
- [Contributing](#contributing)
|
|
29
44
|
- [Bug report](#bug-report)
|
|
30
|
-
- [Emails could not be
|
|
45
|
+
- [Emails could not be decoded](#emails-could-not-be-decoded)
|
|
31
46
|
- [Other Information](#other-information)
|
|
32
47
|
- [Related sites](#related-sites)
|
|
33
48
|
- [See also](#see-also)
|
|
@@ -36,112 +51,151 @@
|
|
|
36
51
|
- [License](#license)
|
|
37
52
|
|
|
38
53
|
What is Sisimai
|
|
39
|
-
|
|
40
|
-
Sisimai is a
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
===================================================================================================
|
|
55
|
+
Sisimai is a library that decodes complex and diverse bounce emails and outputs the results of the
|
|
56
|
+
delivery failure, such as the reason for the bounce and the recipient email address, in structured
|
|
57
|
+
data. It is also possible to output in JSON format. The Ruby version of Sisimai is ported from the
|
|
58
|
+
Perl version of Sisimai at [github.com/sisimai/p5-sisimai](https://github.com/sisimai/p5-sisimai/).
|
|
43
59
|
|
|
44
60
|

|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
62
|
+
The key features of Sisimai
|
|
63
|
+
---------------------------------------------------------------------------------------------------
|
|
64
|
+
* __Decode email bounces to structured data__
|
|
65
|
+
* Sisimai provides detailed insights into bounce emails by extracting 24 key data points.[^2]
|
|
66
|
+
* __Essential information__: `timestamp`, `origin`
|
|
67
|
+
* __Sender information__: `addresser`, `senderdomain`,
|
|
68
|
+
* __Recipient information__: `recipient`, `destination`, `alias`
|
|
69
|
+
* __Delivery information__: `action`, `replycode`,`action`, `replycode`, `deliverystatus`
|
|
70
|
+
* __Bounce details__: `reason`, `diagnosticcode`, `diagnostictype`, `feedbacktype`, `hardbounce`
|
|
71
|
+
* __Message details__: `subject`, `messageid`, `listid`,
|
|
72
|
+
* __Additional information__: `smtpagent`, `timezoneoffset`, `lhost`, `rhost`, `token`, `catch`
|
|
73
|
+
* Output formats
|
|
74
|
+
* Ruby (Hash, Array)
|
|
75
|
+
* JSON
|
|
76
|
+
* (by using [`oj`](https://rubygems.org/gems/oj) gem at CRuby)
|
|
77
|
+
* (by using [`jrjackson`](https://rubygems.org/gems/jrjackson) gem at JRuby)
|
|
78
|
+
* YAML ([`yaml`](https://rubygems.org/gems/yaml) gem required)
|
|
79
|
+
* __Easy to Install, Use.__
|
|
80
|
+
* `gem install`
|
|
81
|
+
* `git clone && make`
|
|
82
|
+
* __High Precision of Analysis__
|
|
83
|
+
* Support [70 MTAs/MDAs/ESPs](https://libsisimai.org/en/engine/)
|
|
84
|
+
* Support Feedback Loop Message(ARF)
|
|
85
|
+
* Can detect [34 bounce reasons](https://libsisimai.org/en/reason/)
|
|
86
|
+
|
|
87
|
+
[^2]: The callback function allows you to add your own data under the `catch` accessor.
|
|
58
88
|
|
|
59
89
|
Command line demo
|
|
60
|
-
|
|
61
|
-
The following screen shows a demonstration of Sisimai at the command line using
|
|
62
|
-
Ruby(rb-sisimai) and
|
|
63
|
-
 and `jq` command.
|
|
93
|
+

|
|
64
94
|
|
|
65
95
|
Setting Up Sisimai
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
===================================================================================================
|
|
68
97
|
System requirements
|
|
69
|
-
|
|
98
|
+
---------------------------------------------------------------------------------------------------
|
|
70
99
|
More details about system requirements are available at
|
|
71
100
|
[Sisimai | Getting Started](https://libsisimai.org/en/start/) page.
|
|
72
101
|
|
|
73
102
|
|
|
74
|
-
* [Ruby 2.
|
|
75
|
-
* [
|
|
103
|
+
* [Ruby 2.4.0 or later](http://www.ruby-lang.org/)
|
|
104
|
+
* [__oj | The fastest JSON parser and object serializer__](https://rubygems.org/gems/oj)
|
|
76
105
|
* Also works on [JRuby 9.0.4.0 or later](http://jruby.org)
|
|
77
|
-
* [
|
|
106
|
+
* [__jrjackson | A mostly native JRuby wrapper for the java jackson json processor jar__](https://rubygems.org/gems/jrjackson)
|
|
78
107
|
|
|
79
108
|
Install
|
|
80
|
-
|
|
109
|
+
---------------------------------------------------------------------------------------------------
|
|
81
110
|
### From RubyGems
|
|
111
|
+
> [!CAUTION]
|
|
112
|
+
> [Sisimai 5](https://github.com/sisimai/p5-sisimai/releases/tag/v5.0.0) has not been uploaded to
|
|
113
|
+
> [RubyGems.org](https://rubygems.org/gems/sisimai) yet as of February 2nd. It will be available on
|
|
114
|
+
> RubyGems.org within a few months, but until then, please clone it from this repository.
|
|
82
115
|
|
|
83
116
|
```shell
|
|
84
117
|
$ sudo gem install sisimai
|
|
85
|
-
Fetching: sisimai-4.25.
|
|
86
|
-
Successfully installed sisimai-4.25.
|
|
87
|
-
Parsing documentation for sisimai-4.25.
|
|
88
|
-
Installing ri documentation for sisimai-4.25.
|
|
118
|
+
Fetching: sisimai-4.25.16.gem (100%)
|
|
119
|
+
Successfully installed sisimai-4.25.16
|
|
120
|
+
Parsing documentation for sisimai-4.25.16
|
|
121
|
+
Installing ri documentation for sisimai-4.25.16
|
|
89
122
|
Done installing documentation for sisimai after 6 seconds
|
|
90
123
|
1 gem installed
|
|
91
124
|
```
|
|
92
125
|
|
|
93
126
|
### From GitHub
|
|
127
|
+
> [!WARNING]
|
|
128
|
+
> Sisimai 5 requires Ruby 2.4 or later. Check the version of Ruby in your system before installing/upgrading
|
|
129
|
+
> by `ruby -v` command.
|
|
94
130
|
|
|
95
131
|
```shell
|
|
132
|
+
% ruby -v
|
|
133
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]
|
|
134
|
+
|
|
96
135
|
$ cd /usr/local/src
|
|
97
136
|
$ git clone https://github.com/sisimai/rb-sisimai.git
|
|
137
|
+
|
|
98
138
|
$ cd ./rb-sisimai
|
|
99
139
|
$ sudo make depend install-from-local
|
|
100
|
-
gem install bundle rake
|
|
140
|
+
gem install bundle rake minitest
|
|
141
|
+
...
|
|
142
|
+
3 gems installed
|
|
143
|
+
if [ -d "/usr/local/jr" ]; then \
|
|
144
|
+
PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/gem install bundle rake minitest; \
|
|
145
|
+
fi
|
|
101
146
|
...
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
sisimai
|
|
105
|
-
sisimai (
|
|
147
|
+
3 gems installed
|
|
148
|
+
/opt/local/bin/rake install
|
|
149
|
+
sisimai 5.0.0 built to pkg/sisimai-5.0.0.gem.
|
|
150
|
+
sisimai (5.0.0) installed.
|
|
151
|
+
if [ -d "/usr/local/jr" ]; then \
|
|
152
|
+
PATH="/usr/local/jr/bin:$PATH" /usr/local/jr/bin/rake install; \
|
|
153
|
+
fi
|
|
154
|
+
sisimai 5.0.0 built to pkg/sisimai-5.0.0-java.gem.
|
|
155
|
+
sisimai (5.0.0) installed.
|
|
106
156
|
```
|
|
107
157
|
|
|
108
158
|
Usage
|
|
109
|
-
|
|
110
|
-
|
|
159
|
+
===================================================================================================
|
|
111
160
|
Basic usage
|
|
112
|
-
|
|
113
|
-
`
|
|
114
|
-
messages
|
|
115
|
-
|
|
116
|
-
|
|
161
|
+
---------------------------------------------------------------------------------------------------
|
|
162
|
+
`Sisimai->rise()` method provides the feature for getting decoded data as Ruby Hash from bounced
|
|
163
|
+
email messages as the following. Beginning with v4.25.6, new accessor `origin` which keeps the path
|
|
164
|
+
to email file as a data source is available.
|
|
117
165
|
|
|
118
166
|
```ruby
|
|
119
167
|
#! /usr/bin/env ruby
|
|
120
168
|
require 'sisimai'
|
|
121
|
-
v = Sisimai.
|
|
169
|
+
v = Sisimai.rise('/path/to/mbox') # or path to Maildir/
|
|
122
170
|
|
|
123
|
-
#
|
|
124
|
-
#
|
|
171
|
+
# In v4.23.0, the rise() and dump() methods of the Sisimai class can now read the entire bounce
|
|
172
|
+
# email as a string, in addition to the PATH to the email file or mailbox.
|
|
125
173
|
f = File.open('/path/to/mbox', 'r'); # or path to Maildir/
|
|
126
|
-
v = Sisimai.
|
|
174
|
+
v = Sisimai.rise(f.read)
|
|
175
|
+
|
|
176
|
+
# If you also need analysis results that are "delivered" (successfully delivered), please specify
|
|
177
|
+
# the "delivered" option to the rise() method as shown below.
|
|
178
|
+
v = Sisimai.rise('/path/to/mbox', delivered: true)
|
|
127
179
|
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
|
|
180
|
+
# From v5.0.0, Sisimai no longer returns analysis results with a bounce reason of "vacation" by
|
|
181
|
+
# default. If you also need analysis results that show a "vacation" reason, please specify the
|
|
182
|
+
# "vacation" option to the rise() method as shown in the following code.
|
|
183
|
+
v = Sisimai.rise('/path/to/mbox', vacation: true );
|
|
131
184
|
|
|
132
185
|
if v.is_a? Array
|
|
133
186
|
v.each do |e|
|
|
134
|
-
puts e.class # Sisimai::
|
|
187
|
+
puts e.class # Sisimai::Fact
|
|
135
188
|
puts e.recipient.class # Sisimai::Address
|
|
136
189
|
puts e.timestamp.class # Sisimai::Time
|
|
137
190
|
|
|
138
|
-
puts e.addresser.address #
|
|
139
|
-
puts e.recipient.address # kijitora@example.jp
|
|
140
|
-
puts e.recipient.host # example.jp
|
|
141
|
-
puts e.deliverystatus # 5.1.1
|
|
142
|
-
puts e.replycode # 550
|
|
143
|
-
puts e.reason # userunknown
|
|
144
|
-
puts e.origin # /var/spool/bounce/Maildir/new/1740074341.eml
|
|
191
|
+
puts e.addresser.address # "michitsuna@example.org" # From
|
|
192
|
+
puts e.recipient.address # "kijitora@example.jp" # To
|
|
193
|
+
puts e.recipient.host # "example.jp"
|
|
194
|
+
puts e.deliverystatus # "5.1.1"
|
|
195
|
+
puts e.replycode # "550"
|
|
196
|
+
puts e.reason # "userunknown"
|
|
197
|
+
puts e.origin # "/var/spool/bounce/Maildir/new/1740074341.eml"
|
|
198
|
+
puts e.hardbounce # true
|
|
145
199
|
|
|
146
200
|
h = e.damn # Convert to HASH
|
|
147
201
|
j = e.dump('json') # Convert to JSON string
|
|
@@ -151,116 +205,209 @@ end
|
|
|
151
205
|
```
|
|
152
206
|
|
|
153
207
|
Convert to JSON
|
|
154
|
-
|
|
155
|
-
`Sisimai.dump()` method provides feature for getting
|
|
156
|
-
|
|
208
|
+
---------------------------------------------------------------------------------------------------
|
|
209
|
+
`Sisimai.dump()` method provides the feature for getting decoded data as JSON string from bounced
|
|
210
|
+
email messages like the following code:
|
|
157
211
|
|
|
158
212
|
```ruby
|
|
159
|
-
# Get JSON string from
|
|
213
|
+
# Get JSON string from path of a mailbox or a Maildir/
|
|
160
214
|
puts Sisimai.dump('/path/to/mbox') # or path to Maildir/
|
|
161
215
|
|
|
162
|
-
# dump() method also accepts "delivered" option like the following code:
|
|
163
|
-
puts Sisimai.dump('/path/to/mbox', delivered: true)
|
|
216
|
+
# dump() method also accepts "delivered" and "vacation" option like the following code:
|
|
217
|
+
puts Sisimai.dump('/path/to/mbox', delivered: true, vacation: true)
|
|
164
218
|
```
|
|
165
219
|
|
|
166
220
|
Callback feature
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
221
|
+
---------------------------------------------------------------------------------------------------
|
|
222
|
+
`:c___` (`c` and three `_`s, looks like a fishhook) argument of `Sisimai.rise` and `Sisimai.dump`
|
|
223
|
+
is an Array and is a parameter to receive Proc objects for callback feature. The first element of
|
|
224
|
+
`:c___` argument is called at `Sisimai::Message.sift` for dealing email headers and entire message
|
|
225
|
+
body. The second element of `:c___` argument is called at the end of each email file parsing. The
|
|
226
|
+
result generated by the callback method is accessible via `Sisimai::Fact.catch`.
|
|
227
|
+
|
|
228
|
+
### [0] For email headers and the body
|
|
229
|
+
Callback method set in the first element of `:c___` is called at `Sisimai::Message.sift()`.
|
|
171
230
|
|
|
172
231
|
```ruby
|
|
173
232
|
#! /usr/bin/env ruby
|
|
174
233
|
require 'sisimai'
|
|
175
|
-
|
|
176
|
-
|
|
234
|
+
code = lambda do |args|
|
|
235
|
+
head = args['headers'] # (*Hash) Email headers
|
|
236
|
+
body = args['message'] # (String) Message body
|
|
237
|
+
adds = { 'x-mailer' => '', 'queue-id' => '' }
|
|
177
238
|
|
|
178
|
-
if cv =
|
|
179
|
-
|
|
239
|
+
if cv = body.match(/^X-Postfix-Queue-ID:\s*(.+)$/)
|
|
240
|
+
adds['queue-id'] = cv[1]
|
|
180
241
|
end
|
|
181
|
-
r['x-mailer'] =
|
|
182
|
-
return
|
|
242
|
+
r['x-mailer'] = head['x-mailer'] || ''
|
|
243
|
+
return adds
|
|
183
244
|
end
|
|
184
245
|
|
|
185
|
-
data = Sisimai.
|
|
186
|
-
json = Sisimai.dump('/path/to/mbox',
|
|
246
|
+
data = Sisimai.rise('/path/to/mbox', c___: [code, nil])
|
|
247
|
+
json = Sisimai.dump('/path/to/mbox', c___: [code, nil])
|
|
187
248
|
|
|
188
|
-
puts data[0].catch['x-mailer']
|
|
249
|
+
puts data[0].catch['x-mailer'] # "Apple Mail (2.1283)"
|
|
250
|
+
puts data[0].catch['queue-id'] # "43f4KX6WR7z1xcMG"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### For each email file
|
|
254
|
+
Callback method set in the second element of `:c___` is called at `Sisimai.rise` method for dealing
|
|
255
|
+
each email file.
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
path = '/path/to/maildir'
|
|
259
|
+
code = lambda do |args|
|
|
260
|
+
kind = args['kind'] # (String) Sisimai::Mail.kind
|
|
261
|
+
mail = args['mail'] # (String) Entire email message
|
|
262
|
+
path = args['path'] # (String) Sisimai::Mail.path
|
|
263
|
+
sisi = args['sisi'] # (Array) List of Sisimai::Fact
|
|
264
|
+
|
|
265
|
+
sisi.each do |e|
|
|
266
|
+
# Store custom information in the "catch" accessor
|
|
267
|
+
e.catch ||= {}
|
|
268
|
+
e.catch['size'] = mail.size
|
|
269
|
+
e.catch['kind'] = kind.capitalize
|
|
270
|
+
|
|
271
|
+
if cv = mail.match(/^Return-Path: (.+)$/)
|
|
272
|
+
# Return-Path: <MAILER-DAEMON>
|
|
273
|
+
e.catch['return-path'] = cv[1]
|
|
274
|
+
end
|
|
275
|
+
e.catch['parsedat'] = Time.new.localtime.to_s
|
|
276
|
+
|
|
277
|
+
# Save the original email with an additional "X-Sisimai-Parsed:" header to a different PATH.
|
|
278
|
+
a = sprintf("X-Sisimai-Parsed: %d", sisi.size)
|
|
279
|
+
p = sprintf("/path/to/another/directory/sisimai-%s.eml", e.token)
|
|
280
|
+
v = mail.sub(/^(From:.+?)$/, '\1' + "\n" + a)
|
|
281
|
+
f = File.open(p, 'w:UTF-8')
|
|
282
|
+
f.write(v)
|
|
283
|
+
f.close
|
|
284
|
+
|
|
285
|
+
# Remove the email file in Maildir/ after decoding
|
|
286
|
+
File.delete(path) if kind == 'maildir'
|
|
287
|
+
|
|
288
|
+
# Need to not return a value
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
list = Sisimai.rise(path, c___: [nil, code])
|
|
293
|
+
|
|
294
|
+
puts list[0].catch['size'] # 2202
|
|
295
|
+
puts list[0].catch['kind'] # "Maildir"
|
|
296
|
+
puts list[0].catch['return-path'] # "<MAILER-DAEMON>"
|
|
189
297
|
```
|
|
190
298
|
|
|
191
299
|
More information about the callback feature is available at
|
|
192
|
-
[Sisimai | How To Parse - Callback](https://libsisimai.org/en/usage/#callback)
|
|
193
|
-
Page.
|
|
300
|
+
[Sisimai | How To Parse - Callback](https://libsisimai.org/en/usage/#callback) Page.
|
|
194
301
|
|
|
195
302
|
One-Liner
|
|
196
|
-
|
|
197
|
-
|
|
303
|
+
---------------------------------------------------------------------------------------------------
|
|
198
304
|
```shell
|
|
199
305
|
% ruby -rsisimai -e 'puts Sisimai.dump($*.shift)' /path/to/mbox
|
|
200
306
|
```
|
|
201
307
|
|
|
202
308
|
Output example
|
|
203
|
-
|
|
204
|
-

|
|
205
311
|
|
|
206
312
|
```json
|
|
207
|
-
[{"
|
|
313
|
+
[{"destination":"google.example.com","lhost":"gmail-smtp-in.l.google.com","hardbounce":0,"reason":"authfailure","catch":null,"addresser":"michitsuna@example.jp","alias":"nekochan@example.co.jp","smtpagent":"Postfix","smtpcommand":"DATA","senderdomain":"example.jp","listid":"","action":"failed","feedbacktype":"","messageid":"hwK7pzjzJtz0RF9Y@relay3.example.com","origin":"./gmail-5.7.26.eml","recipient":"kijitora@google.example.com","rhost":"gmail-smtp-in.l.google.com","subject":"Nyaan","timezoneoffset":"+0900","replycode":550,"token":"84656774898baa90660be3e12fe0526e108d4473","diagnostictype":"SMTP","timestamp":1650119685,"diagnosticcode":"host gmail-smtp-in.l.google.com[64.233.187.27] said: This mail has been blocked because the sender is unauthenticated. Gmail requires all senders to authenticate with either SPF or DKIM. Authentication results: DKIM = did not pass SPF [relay3.example.com] with ip: [192.0.2.22] = did not pass For instructions on setting up authentication, go to https://support.google.com/mail/answer/81126#authentication c2-202200202020202020222222cat.127 - gsmtp (in reply to end of DATA command)","deliverystatus":"5.7.26"}]
|
|
208
314
|
```
|
|
209
315
|
|
|
210
|
-
Sisimai
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
| Features
|
|
221
|
-
|
|
222
|
-
| System requirements
|
|
223
|
-
|
|
|
224
|
-
|
|
|
225
|
-
| The
|
|
226
|
-
|
|
|
227
|
-
| Dependencies (Except
|
|
228
|
-
|
|
|
229
|
-
| The number of tests
|
|
230
|
-
|
|
|
231
|
-
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
316
|
+
Differences between Sisimai 4 and Sisimai 5
|
|
317
|
+
===================================================================================================
|
|
318
|
+
The following table show the differences between [Sisimai 4.25.16p1](https://github.com/sisimai/rb-sisimai/releases/tag/v4.25.16p1)
|
|
319
|
+
and [Sisimai 5](https://github.com/sisimai/rb-sisimai/releases/tag/v5.0.0). More information about
|
|
320
|
+
differences are available at [Sisimai | Differences](https://libsisimai.org/en/diff/) page.
|
|
321
|
+
|
|
322
|
+
Features
|
|
323
|
+
---------------------------------------------------------------------------------------------------
|
|
324
|
+
Beginning with v5.0.0, Sisimai requires **Ruby 2.4.0 or later.**
|
|
325
|
+
|
|
326
|
+
| Features | Sisimai 4 | Sisimai 5 |
|
|
327
|
+
|------------------------------------------------------|--------------------|---------------------|
|
|
328
|
+
| System requirements (CRuby) | 2.1 - | **2.4** - 3.3.0 |
|
|
329
|
+
| System requirements (JRuby) | 9.0.4.0 - | 9.0.4.0 - |
|
|
330
|
+
| Callback feature for the original email file | N/A | Available[^3] |
|
|
331
|
+
| The number of MTA/ESP modules | 68 | 70 |
|
|
332
|
+
| The number of detectable bounce reasons | 29 | 34 |
|
|
333
|
+
| Dependencies (Except Ruby Standard Gems) | 1 gem | 1 gem |
|
|
334
|
+
| Source lines of code | 10,300 lines | 11,300 lines |
|
|
335
|
+
| The number of tests in spec/,test/ directory | 311,000 tests | 336,000 tests |
|
|
336
|
+
| The number of bounce emails decoded/sec (CRuby)[^4] | 231 emails | 305 emails |
|
|
337
|
+
| License | 2 Clause BSD | 2 Caluse BSD |
|
|
338
|
+
| Commercial support | Available | Available |
|
|
339
|
+
|
|
340
|
+
[^3]: The 2nd argument of `:c___` parameter at `Sisimai.rise` method
|
|
341
|
+
[^4]: macOS Monterey/1.6GHz Dual-Core Intel Core i5/16GB-RAM/Ruby 2.6.4p104
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
Decoding Method
|
|
345
|
+
---------------------------------------------------------------------------------------------------
|
|
346
|
+
Some decoding method names, class names, parameter names have been changed at Sisimai 5.
|
|
347
|
+
The details of the decoded data are available at [LIBSISIMAI.ORG/EN/DATA](https://libsisimai.org/en/data/)
|
|
348
|
+
|
|
349
|
+
| Decoding Method | Sisimai 4 | Sisimai 5 |
|
|
350
|
+
|------------------------------------------------------|--------------------|---------------------|
|
|
351
|
+
| Decoding method name | `Sisimai.make` | `Sisimai.rise` |
|
|
352
|
+
| Dumping method name | `Sisimai.dump` | `Sisimai.dump` |
|
|
353
|
+
| Class name of decoded object | `Sisimai::Data` | `Sisimai::Fact` |
|
|
354
|
+
| Parameter name of the callback | `hook` | `:c___`[^5] |
|
|
355
|
+
| Method name for checking the hard/soft bounce | `softbounce` | `hardbounce` |
|
|
356
|
+
| Decode a vacation message by default | Yes | No |
|
|
357
|
+
| Sisimai::Message returns an object | Yes | No |
|
|
358
|
+
| MIME decoding class | `Sisimai::MIME` | `Sisimai::RFC2045` |
|
|
359
|
+
| Decoding transcript of SMTP session | No | Yes[^6] |
|
|
360
|
+
|
|
361
|
+
[^5]: `:c___` looks like a fishhook
|
|
362
|
+
[^6]: `Sisimai::SMTP::Transcript.rise` Method provides the feature
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
MTA/ESP Module Names
|
|
366
|
+
---------------------------------------------------------------------------------------------------
|
|
367
|
+
Three ESP module names have been changed at Sisimai 5. The list of the all MTA/ESP modules is
|
|
368
|
+
available at [LIBSISIMAI.ORG/EN/ENGINE](https://libsisimai.org/en/engine/)
|
|
369
|
+
|
|
370
|
+
| `Sisimai::Rhost::` | Sisimai 4 | Sisimai 5 |
|
|
371
|
+
|------------------------------------------------------|--------------------|---------------------|
|
|
372
|
+
| Microsoft Exchange Online | `ExchangeOnline` | `Microsoft` |
|
|
373
|
+
| Google Workspace | `GoogleApps` | `Google` |
|
|
374
|
+
| Tencent | `TencentQQ` | `Tencent` |
|
|
375
|
+
|
|
376
|
+
Bounce Reasons
|
|
377
|
+
---------------------------------------------------------------------------------------------------
|
|
378
|
+
Five bounce reasons have been added at Sisimai 5. The list of the all bounce reasons sisimai can
|
|
379
|
+
detect is available at [LIBSISIMAI.ORG/EN/REASON](https://libsisimai.org/en/reason/)
|
|
380
|
+
|
|
381
|
+
| Rejected due to | Sisimai 4 | Sisimai 5 |
|
|
382
|
+
|------------------------------------------------------|--------------------|---------------------|
|
|
383
|
+
| sender domain authentication(SPF,DKIM,DMARC) | `SecurityError` | `AuthFailure` |
|
|
384
|
+
| low/bad reputation of the sender hostname/IP addr. | `Blocked` | `BadReputation` |
|
|
385
|
+
| missing PTR/having invalid PTR | `Blocked` | `RequirePTR` |
|
|
386
|
+
| non-compliance with RFC[^7] | `SecurityError` | `NotCompliantRFC` |
|
|
387
|
+
| exceeding a rate limit or sending too fast | `SecurityError` | `Speeding` |
|
|
388
|
+
|
|
389
|
+
[^7]: RFC5322 and related RFCs
|
|
241
390
|
|
|
242
|
-
Contributing
|
|
243
|
-
===============================================================================
|
|
244
391
|
|
|
392
|
+
Contributing
|
|
393
|
+
===================================================================================================
|
|
245
394
|
Bug report
|
|
246
|
-
|
|
247
|
-
Please use the [issue tracker](https://github.com/sisimai/rb-sisimai/issues)
|
|
248
|
-
to report any bugs.
|
|
395
|
+
---------------------------------------------------------------------------------------------------
|
|
396
|
+
Please use the [issue tracker](https://github.com/sisimai/rb-sisimai/issues) to report any bugs.
|
|
249
397
|
|
|
250
|
-
Emails could not be
|
|
251
|
-
|
|
252
|
-
Bounce mails which could not be
|
|
398
|
+
Emails could not be decoded
|
|
399
|
+
---------------------------------------------------------------------------------------------------
|
|
400
|
+
Bounce mails which could not be decoded by Sisimai are saved in the repository
|
|
253
401
|
[set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet](https://github.com/sisimai/set-of-emails/tree/master/to-be-debugged-because/sisimai-cannot-parse-yet).
|
|
254
|
-
If you have found any bounce email cannot be
|
|
255
|
-
|
|
402
|
+
If you have found any bounce email cannot be decoded using Sisimai, please add the email into the
|
|
403
|
+
directory and send Pull-Request to this repository.
|
|
256
404
|
|
|
257
405
|
Other Information
|
|
258
|
-
|
|
259
|
-
|
|
406
|
+
===================================================================================================
|
|
260
407
|
Related Sites
|
|
261
|
-
|
|
408
|
+
---------------------------------------------------------------------------------------------------
|
|
262
409
|
* __@libsisimai__ | [Sisimai on Twitter (@libsisimai)](https://twitter.com/libsisimai)
|
|
263
|
-
*
|
|
410
|
+
* __LIBSISIMAI.ORG__ | [SISIMAI | MAIL ANALYZING INTERFACE | DECODING BOUNCES, BETTER AND FASTER.](https://libsisimai.org/)
|
|
264
411
|
* __Sisimai Blog__ | [blog.libsisimai.org](http://blog.libsisimai.org/)
|
|
265
412
|
* __Facebook Page__ | [facebook.com/libsisimai](https://www.facebook.com/libsisimai/)
|
|
266
413
|
* __GitHub__ | [github.com/sisimai/rb-sisimai](https://github.com/sisimai/rb-sisimai)
|
|
@@ -269,7 +416,7 @@ Related Sites
|
|
|
269
416
|
* __Fixtures__ | [set-of-emails - Sample emails for "make test"](https://github.com/sisimai/set-of-emails)
|
|
270
417
|
|
|
271
418
|
See also
|
|
272
|
-
|
|
419
|
+
---------------------------------------------------------------------------------------------------
|
|
273
420
|
* [README-JA.md - README.md in Japanese(日本語)](https://github.com/sisimai/rb-sisimai/blob/master/README-JA.md)
|
|
274
421
|
* [RFC3463 - Enhanced Mail System Status Codes](https://tools.ietf.org/html/rfc3463)
|
|
275
422
|
* [RFC3464 - An Extensible Message Format for Delivery Status Notifications](https://tools.ietf.org/html/rfc3464)
|
|
@@ -278,14 +425,14 @@ See also
|
|
|
278
425
|
* [RFC5322 - Internet Message Format](https://tools.ietf.org/html/rfc5322)
|
|
279
426
|
|
|
280
427
|
Author
|
|
281
|
-
|
|
428
|
+
===================================================================================================
|
|
282
429
|
[@azumakuniyuki](https://twitter.com/azumakuniyuki)
|
|
283
430
|
|
|
284
431
|
Copyright
|
|
285
|
-
|
|
286
|
-
Copyright (C) 2015-
|
|
432
|
+
===================================================================================================
|
|
433
|
+
Copyright (C) 2015-2024 azumakuniyuki, All Rights Reserved.
|
|
287
434
|
|
|
288
435
|
License
|
|
289
|
-
|
|
436
|
+
===================================================================================================
|
|
290
437
|
This software is distributed under The BSD 2-Clause License.
|
|
291
438
|
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
require 'rspec/core/rake_task'
|
|
2
1
|
require 'bundler/gem_helper'
|
|
2
|
+
|
|
3
3
|
if RUBY_PLATFORM =~ /java/
|
|
4
4
|
filename = 'sisimai-java'
|
|
5
5
|
else
|
|
6
6
|
filename = 'sisimai'
|
|
7
7
|
end
|
|
8
8
|
Bundler::GemHelper.install_tasks :name => filename
|
|
9
|
-
RSpec::Core::RakeTask.new(:spec)
|
|
10
9
|
|
|
11
|
-
task :default => :
|
|
10
|
+
task :default => :test
|
|
11
|
+
task :test => [:publictest, :privatetest]
|
|
12
|
+
task :publictest do
|
|
13
|
+
Dir.glob('./test/public/*-test.rb').each { |cf| require cf }
|
|
14
|
+
end
|
|
15
|
+
task :privatetest do
|
|
16
|
+
Dir.glob('./test/private/*-test.rb').each { |cf| require cf }
|
|
17
|
+
end
|
|
12
18
|
|
data/Repository.mk
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# | _ < __/ |_) | (_) \__ \ | || (_) | | | |_| |_| | | | | | <
|
|
6
6
|
# |_| \_\___| .__/ \___/|___/_|\__\___/|_| \__, (_)_| |_| |_|_|\_\
|
|
7
7
|
# |_| |___/
|
|
8
|
-
#
|
|
8
|
+
# -------------------------------------------------------------------------------------------------
|
|
9
9
|
SHELL := /bin/sh
|
|
10
10
|
GIT ?= git
|
|
11
11
|
CP := cp
|
|
@@ -16,8 +16,7 @@ EMAILS = set-of-emails
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
.DEFAULT_GOAL = git-status
|
|
19
|
-
|
|
20
|
-
# -----------------------------------------------------------------------------
|
|
19
|
+
# -------------------------------------------------------------------------------------------------
|
|
21
20
|
.PHONY: clean
|
|
22
21
|
|
|
23
22
|
git-status:
|