// bdlde_quotedprintableencoder.h -*-C++-*- #ifndef INCLUDED_BDLDE_QUOTEDPRINTABLEENCODER #define INCLUDED_BDLDE_QUOTEDPRINTABLEENCODER #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide automata converting to and from Quoted-Printable encodings. // //@CLASSES: // bdlde::QuotedPrintableEncoder: automaton for Quoted-Printable encoding // //@SEE_ALSO: bdlde_quotedprintabledecoder // //@DESCRIPTION: This component provides a class that can be used to encode byte // sequences of arbitrary length into the Quoted Printable representation // described in Section 6.7 "Quoted-Printable Content Transfer Encoding" of RFC // 2045, "Multipurpose Internet Mail Extensions (MIME) Part One: Format of // Internet Message Bodies." // // Each instance of the encoder retains the state of the conversion from one // supplied input to the next, enabling the processing of segmented input -- // i.e., processing resumes where it left off with the next invocation on new // input. Instance methods are provided for the encoder to (1) assert the end // of input, (2) determine whether the input so far is currently acceptable, // and (3) indicate whether a non-recoverable error has occurred. // ///Quoted-Printable Encoding ///------------------------- // This encoding scheme is suitable for encoding arbitrary data consisting // primarily of printable text characters. Additionally, this scheme seeks to // preserve the integrity of the byte stream during transfer by making it // difficult for any intermediate interpreting software in the path of the // transfer to disruptively change its content (e.g., because of trailing // whitespace and line breaks). For binary data, Base64 encoding may be a more // appropriate scheme (see 'bdede_base64'). // // The data stream is processed one byte at a time from left to right as // follows: // ///General 8-Bit Representation /// - - - - - - - - - - - - - - // Any 8-bit input character, except a CR or LF, *may* be represented by an "=" // followed by a 2-digit hexadecimal representation of its ASCII value. Only // uppercase hexadecimal digits are allowed. For example, the letter 'n' can // be encoded into '=6E'. // ///Literal Representation /// - - - - - - - - - - - // Characters with decimal values in the range [33..126], with the exception of // 61 ('='), *may* be represented literally as they appear before encoding. // Hence, in addition to [0-9][a-z][A-Z], the following characters may // propagate to the encoded stream unchanged. //.. // [!"#$%&'()*+,-./:;<>?@[\]^_`{|}~] //.. // ///Whitespace /// - - - - - // Space and tab *may* be represented literally, unless they appear at the end // of an encoded line, in which case they must be followed by a '=' character // serving as a soft line break (see rule #5), or they must be encoded // according to rule #1. It follows that any trailing whitespace encountered // in a Quoted-Printable body must necessarily be added by intermediate // transport agents and must be deleted during decoding. // ///Line Breaks ///- - - - - - // A line break must be represented in the Quoted-Printable encoding as in rule // number 1, i.e., LF -> =0A; CR -> =0D. // ///Soft Line Breaks /// - - - - - - - - // Encoded lines are required to be no longer than 76 characters in this // encoding scheme. Soft line breaks in the form of an '=' sign placed at the // end of an encoded line are used to break up longer lines, either necessarily // when the number of encoded characters, including any '=' characters but not // counting the trailing CRLF, reaches the limit of 76, or at the user's // discretion -- e.g., during manual encoding. Soft line breaks are to be // removed during decoding as they are not part of the original content. // // The Quoted-Printable encoding scheme allows one or two forms of encoding // depending on the value of the character to be encoded as well as its // location with respect to the end of line. When both forms are permissible, // the choice is discretionary. For example, the word 'From' is often used as // a message separator in the standard UNIX mail folder format. To reduce the // chance of a message getting broken, a sentence such as "From point A ..." is // often best encoded as "=46rom point A ...", although "From point A ..." is // also a valid encoding. // // This implementation by default prefers literal encoding to Quoted-Printable // encoding. In the case of a space or tab character happening at the end of // an encoded line, if there is more input to follow, a soft line break is // inserted; otherwise, the last line of encoding should be terminated with the // Quoted Printable encoding of space or tab (a line break is both redundant // and contrived). // // In situations where it is desirable to specify certain characters to be // encoded to their numeric form, the encoder in this implementation also // offers a means to specify these characters through the first parameter to // the following constructor //.. // bdlde::QuotedPrintableEncoder( // const char *extraCharsToEncode, // bdlde::QuotedPrintableEncoder::LineBreakMode lineBreakMode // = bdlde::QuotedPrintableEncoder::BDEDE_CRLF_MODE, // int maxLineLength = // bdlde::QuotedPrintableEncoder::DEFAULT_MAX_LINELEN); //.. // // The following examples demonstrate the above rules per the design choices // made for this implementation. Note that there is a hard line break at the // 77th character position, immediately after "dozing". // ///Example 1 ///- - - - - // Data: //.. // From point A to point B, the distance is 1245.56 miles. Driving at a dozing // speed of 15mph, it will take 2 hours to complete the trip. //.. // // Encoding: //.. // =46rom point A to point B, the distance is 1245.56 miles. Driving at a doz= // ing=0D=0A speed of 15mph, = // it will take 2 hours= // to complete the trip. //.. // ///Example 2 ///- - - - - // Data: //.. // Hello, world. //.. // (The last line of input ends with a whitespace.) // // Encoding: //.. // Hello, world.=20 //.. // (In this case, a Quoted Printable is preferred to soft line break as there // should only be one encoded line.) // // The above encoding is acceptable, although it is by no means unique. // ///Quoted-Printable Decoding ///------------------------- // (In the following, all rules mentioned refer to those listed in the encoder // section above.) // // The decoding process for this encoding scheme involves: // //: 1 transforming any encoded character triplets back into their original //: representation (rule #1 and rule #4). //: //: 2 literally writing out characters that have not been changed (rule #2). //: //: 3 deleting any trailing whitespace at the end of an encoded line (rule #3). //: //: 4 removing the soft line breaks including the '=' prefix (i.e., //: concatenating broken sentences) (rule #5). // // The standard imposes a maximum of 76 characters exclusive of CRLF; however, // the decoder implemented in this component will handle lines of arbitrary // length. // // The decoder also provides support for two error-reporting modes, // configurable at construction: the strict mode and the relaxed mode. A // strict-mode decoder stops decoding at the first offending character // encountered, while a relaxed-mode decoder continues decoding to the end of // the input, allowing straight pass-through of character sets that cannot be // interpreted. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Encoding ///- - - - - - - - - - // The following example shows how to use a 'bdlde::QuotedPrintableEncoder' // object to implement a function, 'streamconverter', that reads text from a // 'bsl::istream', encodes that text in Quoted-Printable representation, and // write928s the encoded text to a 'bsl::ostream'. 'streamconverter' returns 0 // on success, and a negative value if the input data could not be successfully // encoded or if there is an I/O error. //.. // streamconverter.h -*-C++-*- // // int streamconverter(bsl::ostream& os, bsl::istream& is); // // Read the entire contents of the specified input stream 'is', convert // // the input plain text to quoted-printable encoding, and write the // // encoded text to the specified output stream 'os'. Return 0 on // // success, and a negative value otherwise. //.. // We will use fixed-sized input and output buffers in the implementation, but, // because of the flexibility of 'bsl::istream' and the output-buffer // monitoring functionality of 'QuotedPrintableEncoder', the fixed buffer sizes // do *not* limit the quantity of data that can be read, encoded, or written to // the output stream. The implementation file is as follows. //.. // streamconverter.cpp -*-C++-*- // // #include <streamconverter.h> // // #include <bdlde_quotedprintableencoder.h> // // namespace BloombergLP { // // int streamconverter(bsl::ostream& os, bsl::istream& is) // { // enum { // SUCCESS = 0, // ENCODE_ERROR = -1, // IO_ERROR = -2 // }; //.. // We declare a 'bdlde::QuotedPrintableEncoder' object 'converter', which will // encode the input data. Note that various internal buffers and cursors are // used as needed without further comment. We read as much data as is // available from the user-supplied input stream 'is' *or* as much as will fit // in 'inputBuffer' before beginning conversion. //.. // bdlde::QuotedPrintableEncoder converter; // // const int INBUFFER_SIZE = 1 << 10; // const int OUTBUFFER_SIZE = 1 << 10; // // char inputBuffer[INBUFFER_SIZE]; // char outputBuffer[OUTBUFFER_SIZE]; // // char *output = outputBuffer; // char *outputEnd = outputBuffer + sizeof outputBuffer; // // while (is.good()) { // input stream not exhausted // // is.read(inputBuffer, sizeof inputBuffer); //.. // With 'inputBuffer' now populated, we'll use 'converter' in an inner 'while' // loop to encode the input and write the encoded data to 'outputBuffer' (via // the 'output' cursor'). Note that if the call to 'converter.convert' fails, // our function terminates with a negative status. //.. // const char *input = inputBuffer; // const char *inputEnd = input + is.gcount(); // // while (input < inputEnd) { // input encoding not complete // // int numOut; // int numIn; // // int status = converter.convert(output, &numOut, &numIn, // input, inputEnd, // outputEnd - output); // if (status < 0) { // return ENCODE_ERROR; // RETURN // } //.. // If the call to 'converter.convert' returns successfully, we'll see if the // output buffer is full, and if so, write its contents to the user-supplied // output stream 'os'. Note how we use the values of 'numOut' and 'numIn' // generated by 'convert' to update the relevant cursors. //.. // output += numOut; // input += numIn; // // if (output == outputEnd) { // output buffer full; write data // os.write (outputBuffer, sizeof outputBuffer); // if (os.fail()) { // return IO_ERROR; // RETURN // } // output = outputBuffer; // } // } // } //.. // We have now exited both the input and the "encode" loops. 'converter' may // still hold encoded output characters, and so we call 'converter.endConvert' // to emit any retained output. To guarantee correct behavior, we call this // method in an infinite loop, because it is possible that the retained output // can fill the output buffer. In that case, we solve the problem by writing // the contents of the output buffer to 'os' within the loop. The most likely // case, however, is that 'endConvert' will return 0, in which case we exit the // loop and write any data remaining in 'outputBuffer' to 'os'. As above, if // 'endConvert' fails, we exit the function with a negative return status. //.. // for (;;) { // // int more = // converter.endConvert(output, &numOut, outputEnd - output); // if (more < 0) { // return ENCODE_ERROR; // RETURN // } // // output += numOut; // // if (!more) { // no more output // break; // } // // assert (output == outputEnd); // output buffer is full // // os.write (outputBuffer, sizeof outputBuffer); // write buffer // if (os.fail()) { // return IO_ERROR; // RETURN // } // output = outputBuffer; // } // // if (output > outputBuffer) { // still data in output buffer; write it // // all // os.write(outputBuffer, output - outputBuffer); // } // // return is.eof() && os.good() ? SUCCESS : IO_ERROR; // } // // } // Close namespace BloombergLP //.. // For ease of reading, we repeat the full content of the 'streamconverter.cpp' // file without interruption. //.. // streamconverter.cpp -*-C++-*- // // #include <streamconverter.h> // // #include <bdlde_quotedprintableencoder.h> // // namespace BloombergLP { // // int streamconverter(bsl::ostream& os, bsl::istream& is) // { // enum { // SUCCESS = 0, // ENCODE_ERROR = -1, // IO_ERROR = -2 // }; // // bdlde::QuotedPrintableEncoder converter; // // const int INBUFFER_SIZE = 1 << 10; // const int OUTBUFFER_SIZE = 1 << 10; // // char inputBuffer[INBUFFER_SIZE]; // char outputBuffer[OUTBUFFER_SIZE]; // // char *output = outputBuffer; // char *outputEnd = outputBuffer + sizeof outputBuffer; // // while (is.good()) { // input stream not exhausted // // is.read(inputBuffer, sizeof inputBuffer); // // const char *input = inputBuffer; // const char *inputEnd = input + is.gcount(); // // while (input < inputEnd) { // input encoding not complete // // int numOut; // int numIn; // // int status = converter.convert(output, &numOut, &numIn, // input, inputEnd, // outputEnd - output); // if (status < 0) { // return ENCODE_ERROR; // RETURN // } // // output += numOut; // input += numIn; // // if (output == outputEnd) { // output buffer full; write data // os.write(outputBuffer, sizeof outputBuffer); // if (os.fail()) { // return IO_ERROR; // RETURN // } // output = outputBuffer; // } // } // } // // for (;;) { // // int more = // converter.endConvert(output, &numOut, outputEnd - output); // if (more < 0) { // return ENCODE_ERROR; // RETURN // } // // output += numOut; // // if (!more) { // no more output // break; // } // // assert (output == outputEnd); // output buffer is full // // os.write (outputBuffer, sizeof outputBuffer); // write buffer // if (os.fail()) { // return IO_ERROR; // RETURN // } // output = outputBuffer; // } // // if (output > outputBuffer) { // os.write (outputBuffer, output - outputBuffer); // } // // return is.eof() && os.good() ? SUCCESS : IO_ERROR; // } // // } // Close namespace BloombergLP //.. #include <bdlscm_version.h> #include <bslma_allocator.h> #include <bslma_usesbslmaallocator.h> #include <bsl_climits.h> // INT_MAX #ifndef BDE_DONT_ALLOW_TRANSITIVE_INCLUDES #include <bslalg_typetraitusesbslmaallocator.h> #endif // BDE_DONT_ALLOW_TRANSITIVE_INCLUDES namespace BloombergLP { namespace bdlde { // ============================ // class QuotedPrintableEncoder // ============================ class QuotedPrintableEncoder { // This class implements a mechanism capable of converting data of // arbitrary length to its corresponding Quoted-Printable representation. // PRIVATE TYPES enum States { // symbolic state values for the encoder e_ERROR_STATE = -1, // input is irreparably invalid e_INITIAL_STATE = 0, // require no more input e_INPUT_STATE = 1, // general input state e_DONE_STATE = 2 // accepting; any additional input is error }; enum { k_DEFAULT_MAX_LINELEN = 76 // maximum allowed by RFC 2045 }; public: // PUBLIC TYPES enum EquivalenceClass { // The input equivalence classes e_PC = 0, // printable character - copy straight to output e_CR, // carriage return - wait for more input e_LF, // line feed - complete linebreak e_WS, // whitespace - buffer; wait for more input e_CC // control character - encode to Quoted Printable #ifndef BDE_OMIT_INTERNAL_DEPRECATED , BDEDE_PC = e_PC , BDEDE_CR = e_CR , BDEDE_LF = e_LF , BDEDE_WS = e_WS , BDEDE_CC = e_CC #endif // BDE_OMIT_INTERNAL_DEPRECATED }; enum LineBreakMode { // Configuration governing how various forms of line breaks are to be // interpreted e_CRLF_MODE = 0, // allow "\r\n" as linebreaks e_LF_MODE, // allow '\n' as linebreaks (without the '\r' prefix) e_MIXED_MODE, // allow "\r\n" and '\n' as linebreaks e_BINARY_MODE // allow no linebreaks #ifndef BDE_OMIT_INTERNAL_DEPRECATED , BDEDE_CRLF_MODE = e_CRLF_MODE , BDEDE_LF_MODE = e_LF_MODE , BDEDE_MIXED_MODE = e_MIXED_MODE #endif // BDE_OMIT_INTERNAL_DEPRECATED }; private: // CLASS DATA static const char *s_defaultEquivClass_p; // default map of // 'unsigned char' to // equivalent class static const char *s_lineBreakModeName[]; // names of line break mode // INSTANCE DATA LineBreakMode d_lineBreakMode; // linebreak mode int d_maxLineLength; // maximum length of output line int d_outputLength; // total number of output characters int d_lineLength; // number of characters on the current //line char *d_equivClass_p; // map of 'unsigned char' to input // equivalence class; dynamically // allocated if the default map is to // be modified; otherwise it is // assigned; compare with static // address to know whether to delete char d_lastInputChar; // stores an input space or tab if it // happens at the end of input int d_state; // stores current state of this object char d_buffer[5]; // stack of characters to output int d_bufferLength; // size of the stack int d_lineStart; // index of output character that // starts the current line char d_deffered; // stores 0 or the deffered input // character bool d_lastWasWS; // stores whether last printed // character was a whitespace character bslma::Allocator *d_allocator_p; // memory allocator (held, not owned) private: // PRIVATE MANIPULATORS void appendSoftLineBreak(char *out); // Append to the buffer addressed by the specified 'out' the first // character of the soft line-break character sequence and push onto // the buffered output stack the characters representing the residual // of the soft line-break character sequence. void appendHardLineBreak(char *out); // Append to the buffer addressed by the specified 'out' the first // character of the hard line-break character sequence and push onto // the buffered output stack the characters representing the residual // of the hard line-break character sequence. void appendPrintable(char *out, char ch); // Append to the buffer addressed by the specified 'out' or, through // the buffered output stack, schedule to append the characters // representing, if needed, a soft line-break and the specified // character 'ch'. void appendAsHex(char *out, char ch, bool isFinal = false); // Append to the buffer addressed by the specified 'out' or, through // the buffered output stack, schedule to append the characters // representing, if needed, a soft line-break, with consideration for // whether or not this is the final chanracters in the encoding as per // the specified 'isFinal', and the specified character 'ch' // represented as a hexadecimal encoding. // NOT IMPLEMENTED QuotedPrintableEncoder(const QuotedPrintableEncoder&); QuotedPrintableEncoder& operator=(const QuotedPrintableEncoder&); public: // CLASS METHODS static const char* lineBreakModeToAscii( QuotedPrintableEncoder::LineBreakMode mode); // Return the string representation exactly matching the enumerator // name corresponding to the specified enumerator 'mode'. // CREATORS explicit QuotedPrintableEncoder( LineBreakMode lineBreakMode = e_CRLF_MODE, int maxLineLength = k_DEFAULT_MAX_LINELEN, bslma::Allocator *basicAllocator = 0); // Create a Quoted-Printable encoder in the initial state, configured // to accept hard line breaks based on the optionally specified // 'lineBreakMode', and to insert soft line breaks when the line length // exceeds the optionally specified 'maxLineLength' (default is the RFC // 2045 maximum 76). Optionally specify a 'basicAllocator' used to // supply memory. If 'basicAllocator' is 0, the currently installed // default allocator is used. The behavior is undefined unless // '4 <= maxLineLength <= 76'. Note that BDEDE_CRLF_MODE passes "\r\n" // straight to output and converts '\n'; BDEDE_LF_MODE passes '\n' and // converts '\r'; BDEDE_MIXED_MODE passes both "\r\n" and '\n'. explicit QuotedPrintableEncoder( const char *extraCharsToEncode, LineBreakMode lineBreakMode = e_CRLF_MODE, int maxLineLength = k_DEFAULT_MAX_LINELEN, bslma::Allocator *basicAllocator = 0); // Create a Quoted-Printable encoder in the initial state, configured // to convert to the form "=XX" any input character matching a // printable or whitespace character in the specified // 'extraCharsToEncode' array (as opposed to the default setting of // passing the input character straight to output), to accept hard // linebreaks based on the optionally specified 'lineBreakMode', and to // insert soft linebreaks when the line length exceeds the optionally // specified 'maxLineLength' (default is the RFC 2045 maximum 76). // Optionally specify a 'basicAllocator' used to supply memory. If // 'basicAllocator' is 0, the currently installed default allocator is // used. The behavior is undefined unless '4 <= 'maxLineLength <= 76'. // Note that BDEDE_CRLF_MODE passes "\r\n" straight to output and // converts '\n'; BDEDE_LF_MODE passes '\n' and converts '\r'; // BDEDE_MIXED_MODE passes both "\r\n" and '\n'. ~QuotedPrintableEncoder(); // Destroy this object. // MANIPULATORS int convert(char *out, int *numOut, int *numIn, const char *begin, const char *end, int maxNumOut = -1); // Append to the buffer addressed by the specified 'out' all pending // output (if there is any) up to the optionally specified 'maxNumOut' // limit (default is negative, meaning no limit) and, when there is no // pending output and 'maxNumOut' is still not reached, begin to // consume and encode a sequence of input characters starting at the // specified 'begin' position, up to but not including the specified // 'end' position, writing any resulting output in the specified 'out' // buffer up to the (cumulative) 'maxNumOut' limit. If 'maxNumOut' // limit is reached, no further input will be consumed. Load into the // specified 'numOut' and 'numIn' the number of output bytes produced // and input bytes consumed, respectively. Return a non-negative value // on success and a negative value otherwise. A successful return // status indicates the number of characters that would be output if // 'endConvert' were called subsequently with no output limit. These // bytes *may* be available for output if this method is called with a // sufficiently large 'maxNumOut'. Note that calling this method after // 'endConvert' has been invoked without an intervening 'reset' call // will place this instance in an error state, and return an error // status. Note also that it is recommended that after all calls to // 'convert' are finished, the 'endConvert' method be called to // complete the encoding of any unprocessed input characters. int endConvert(char *out, int *numOut, int maxNumOut = -1); // Terminate encoding for this encoder; write any retained output // (e.g., from a previous call to 'convert' with a non-zero 'maxNumOut' // argument) to the specified 'out' buffer. Optionally specify the // 'maxNumOut' limit on the number of bytes to output; if 'maxNumOut' // is negative, no limit is imposed. Load into the specified 'numOut' // the number of output bytes produced. Return a non-negative value on // success and a negative value otherwise. A successful return status // indicates the number of characters that would be output if // 'endConvert' were called subsequently with no output limit. Any // retained bytes are available on a subsequent call to 'endConvert'. // Once this method is called, no additional input may be supplied // without an intervening call to 'reset'; once this method returns a // zero status, a subsequent call will place this encoder in the error // state, and return an error status. void reset(); // Reset this encoder to its initial state (i.e., as if no input had // been consumed). // ACCESSORS bool isAccepting() const; // Return 'true' if the input read so far by this encoder is considered // syntactically complete, and 'false' otherwise. bool isDone() const; // Return 'true' if this encoder is in the done state (i.e., // 'endConvert' has been called and any additional input will result in // an error), and if there is no pending output, and 'false' otherwise. bool isError() const; // Return 'true' if there is no possibility of achieving an // "acceptable" result, and 'false' otherwise. Note that for an // encoder, no input can cause an error; the possible errors result // either from a call to the 'convert' method after the 'endConvert' // method is called the first time, or from a call to either the // 'convert' or the 'endConvert' methods after the 'endConvert' method // has returned successfully. bool isInitialState() const; // Return 'true' if this encoder is in the initial state (i.e., as if // no input had been consumed), and 'false' otherwise. LineBreakMode lineBreakMode() const; // Return the line break mode configured at the construction of this // encoder. int maxLineLength() const; // Return the value for the maximum line length configured at the // construction of this encoder. int numOutputPending() const; // Return the number of characters that would be output if 'endConvert' // were called with no output limit. int outputLength() const; // Return the total length of the output emitted by this encoder // (possibly after one or more calls to the 'convert' or the 'input' // methods) since its initial construction or the latest 'reset'. Note // that soft line breaks are included in the counts if added. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // CLASS METHODS inline const char* QuotedPrintableEncoder::lineBreakModeToAscii(LineBreakMode mode) { return s_lineBreakModeName[mode]; } // MANIPULATORS inline void QuotedPrintableEncoder::reset() { d_state = e_INITIAL_STATE; d_outputLength = 0; d_lineLength = 0; d_bufferLength = 0; d_deffered = 0; } // ACCESSORS inline bool QuotedPrintableEncoder::isAccepting() const { return e_ERROR_STATE != d_state; } inline bool QuotedPrintableEncoder::isDone() const { return e_DONE_STATE == d_state && 0 == d_deffered && 0 == d_bufferLength; } inline bool QuotedPrintableEncoder::isError() const { return e_ERROR_STATE == d_state; } inline bool QuotedPrintableEncoder::isInitialState() const { return e_INITIAL_STATE == d_state; } inline QuotedPrintableEncoder::LineBreakMode QuotedPrintableEncoder::lineBreakMode() const { return d_lineBreakMode; } inline int QuotedPrintableEncoder::maxLineLength() const { return d_maxLineLength; } inline int QuotedPrintableEncoder::numOutputPending() const { return d_deffered ? 3 : d_bufferLength; } inline int QuotedPrintableEncoder::outputLength() const { return d_outputLength; } } // close package namespace } // close enterprise namespace // TRAITS namespace BloombergLP { namespace bslma { template <> struct UsesBslmaAllocator<bdlde::QuotedPrintableEncoder> : bsl::true_type {}; } // close namespace bslma } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2016 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------