1 /** 2 (module summary) 3 4 Copyright: © 2012-2014 RejectedSoftware e.K. 5 License: Subject to the terms of the General Public License version 3, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module vibenews.nntp.common; 9 10 import vibe.core.log; 11 import vibe.stream.operations; 12 13 import std.algorithm; 14 import std.exception; 15 16 17 class NNTPBodyReader : InputStream { 18 private { 19 InputStreamProxy m_stream; 20 bool m_eof = false; 21 ubyte[] m_currLine; 22 } 23 24 this(InputStreamProxy stream) 25 { 26 m_stream = stream; 27 readNextLine(); 28 } 29 30 @property bool empty() { return m_eof; } 31 32 @property ulong leastSize() { return m_currLine.length; } 33 34 @property bool dataAvailableForRead(){ 35 return m_currLine.length > 0; 36 } 37 38 @property ubyte[] peek() 39 { 40 return m_currLine; 41 } 42 43 static if (!is(IOMode)) override void read(scope ubyte[] bytes) { readImpl(bytes); } 44 else override size_t read(scope ubyte[] bytes, IOMode) { readImpl(bytes); return bytes.length; } 45 alias read = InputStream.read; 46 47 private void readImpl()(scope ubyte[] dst) 48 { 49 while (dst.length > 0) { 50 enforce(!m_eof); 51 auto amt = min(dst.length, m_currLine.length); 52 dst[0 .. amt] = m_currLine[0 .. amt]; 53 54 m_currLine = m_currLine[amt .. $]; 55 dst = dst[amt .. $]; 56 57 if( m_currLine.length == 0 ) 58 readNextLine(); 59 } 60 } 61 62 private void readNextLine()() 63 { 64 enforce(!m_eof); 65 m_currLine = m_stream.readLine() ~ cast(const(ubyte)[])"\r\n"; 66 m_eof = m_currLine == ".\r\n"; 67 if( m_currLine.startsWith("..") ) m_currLine = m_currLine[1 .. $]; 68 } 69 } 70 71 deprecated alias NntpBodyReader = NNTPBodyReader; 72 73 74 class NNTPBodyWriter : OutputStream { 75 private { 76 OutputStreamProxy m_stream; 77 bool m_finalized = false; 78 int m_lineState = 0; 79 static immutable ubyte[] m_lineStateString = cast(immutable(ubyte)[])"\r\n."; 80 bool m_empty = true; 81 } 82 83 this(OutputStreamProxy stream) 84 { 85 m_stream = stream; 86 } 87 88 static if (!is(IOMode)) override void write(in ubyte[] bytes) { writeImpl(bytes); } 89 else override size_t write(in ubyte[] bytes, IOMode) { writeImpl(bytes); return bytes.length; } 90 alias write = OutputStream.write; 91 92 private void writeImpl()(in ubyte[] bytes_) 93 { 94 const(ubyte)[] bytes = bytes_; 95 assert(!m_finalized); 96 97 if( bytes.length ){ 98 if( m_empty && bytes[0] == '.' ){ 99 m_stream.write(".."); 100 logDebugV("WS <..>", cast(const(char)[])bytes[0 .. $-m_lineState]); 101 bytes = bytes[1 .. $]; 102 } 103 m_empty = false; 104 } 105 106 // test any already started prefix 107 if( m_lineState > 0 ){ 108 foreach( i; m_lineState .. min(m_lineStateString.length, bytes.length+m_lineState) ){ 109 if( bytes[i-m_lineState] != m_lineStateString[i] ){ 110 m_stream.write(m_lineStateString[0 .. i]); 111 bytes = bytes[i-m_lineState .. $]; 112 logDebugV("WPM <%s>", cast(const(char)[])m_lineStateString[0 .. i]); 113 m_lineState = 0; 114 break; 115 } 116 } 117 if( m_lineState > 0 ){ 118 if( m_lineStateString.length > bytes.length+m_lineState ){ 119 m_lineState += bytes.length; 120 bytes = null; 121 } else { 122 m_stream.write("\r\n.."); 123 logDebugV("WEM <\\r\\n..>"); 124 bytes = bytes[m_lineStateString.length-m_lineState .. $]; 125 m_lineState = 0; 126 } 127 } 128 } 129 130 while( bytes.length ){ 131 auto idx = bytes.countUntil(m_lineStateString); 132 if( idx >= 0 ){ 133 m_stream.write(bytes[0 .. idx]); 134 m_stream.write("\r\n.."); 135 logDebugV("WMM <%s\\r\\n..>", cast(const(char)[])bytes[0 .. idx]); 136 bytes = bytes[idx+m_lineStateString.length .. $]; 137 } else { 138 foreach( i; 1 .. min(m_lineStateString.length, bytes.length) ) 139 if( bytes[$-i .. $] == m_lineStateString[0 .. i] ){ 140 m_lineState = cast(int)i; 141 break; 142 } 143 m_stream.write(bytes[0 .. $-m_lineState]); 144 logDebugV("WP <%s>", cast(const(char)[])bytes[0 .. $-m_lineState]); 145 bytes = null; 146 } 147 } 148 } 149 150 void flush() 151 { 152 m_stream.flush(); 153 } 154 155 void finalize() 156 { 157 if( m_lineState > 0 ) m_stream.write(m_lineStateString[0 .. m_lineState]); 158 enforce(!m_finalized); 159 m_finalized = true; 160 if( m_empty ) m_stream.write(".\r\n"); 161 else m_stream.write("\r\n.\r\n"); 162 m_stream.flush(); 163 logDebugV("WF <\\r\\n.\\r\\n>"); 164 } 165 166 static if (!is(IOMode)) { 167 override void write(InputStream stream, ulong nbytes = 0) 168 { 169 writeDefault(stream, nbytes); 170 } 171 } 172 } 173 174 deprecated alias NntpBodyWriter = NNTPBodyReader;