#include "qargs.h" #include namespace NUri { namespace NOnStackArgsList { struct TQArgNode { TQArgNode* Prev; TQArgNode* Next; TStringBuf Name; TStringBuf Value; TStringBuf All; }; TQArgNode MakeArg(TQArgNode* prev) { return {prev, 0, {}, {}, {}}; } const char* SkipDelimiter(const char* str, const char* end) { while (str != end) if (*str == '&') ++str; else break; return str; } /// return next pos or 0 if error const char* ExtractArgData(const char* pos, const char* end, TQArgNode* arg) { const char* nameStart = pos; const char* nextArg = strchr(pos, '&'); const char* valueStart = strchr(pos, '='); if (valueStart && nextArg && valueStart < nextArg) // a=1& or a=& { arg->Name = TStringBuf(nameStart, valueStart - nameStart); arg->Value = TStringBuf(valueStart + 1, nextArg - valueStart - 1); arg->All = TStringBuf(nameStart, nextArg - nameStart); return nextArg; } else if (valueStart && nextArg && valueStart > nextArg) // a&b=2 { arg->Name = TStringBuf(nameStart, nextArg - nameStart); arg->All = arg->Name; return nextArg; } else if (valueStart && !nextArg) // a=1 or a= { arg->Name = TStringBuf(nameStart, valueStart - nameStart); arg->Value = TStringBuf(valueStart + 1, end - valueStart - 1); arg->All = TStringBuf(nameStart, end - nameStart); return end; } else if (!valueStart && nextArg) // a&b { arg->Name = TStringBuf(nameStart, nextArg - nameStart); arg->All = arg->Name; return nextArg; } else { // a arg->Name = TStringBuf(nameStart, end - nameStart); arg->All = arg->Name; return end; } } // arg can be null TQArgNode* GetHead(TQArgNode* arg) { TQArgNode* prev = arg; while (prev) { arg = prev; prev = prev->Prev; } return arg; } // arg can be null TQArgNode* GetLast(TQArgNode* arg) { TQArgNode* next = arg; while (next) { arg = next; next = arg->Next; } return arg; } int CompareName(const TQArgNode* l, const TQArgNode* r) { return l->Name.compare(r->Name); } TQArgNode* Move(TQArgNode* before, TQArgNode* node) { TQArgNode* tn = node->Next; TQArgNode* tp = node->Prev; node->Prev = before->Prev; if (node->Prev) node->Prev->Next = node; node->Next = before; before->Prev = node; if (tn) tn->Prev = tp; if (tp) tp->Next = tn; return node; } // return new head TQArgNode* QSortByName(TQArgNode* iter, TQArgNode* last) { if (iter == last) return iter; if (iter->Next == last) { int c = CompareName(iter, last); return c <= 0 ? iter : Move(iter, last); } else { TQArgNode* pivot = iter; iter = iter->Next; TQArgNode* head = 0; TQArgNode* tail = 0; TQArgNode* tailPartitionStart = pivot; while (true) { TQArgNode* next = iter->Next; int c = CompareName(iter, pivot); int sign = (0 < c) - (c < 0); switch (sign) { case -1: head = head ? Move(head, iter) : Move(pivot, iter); break; case 0: pivot = Move(pivot, iter); break; case 1: tail = iter; break; } if (iter == last) break; iter = next; } if (head) head = QSortByName(head, pivot->Prev); if (tail) QSortByName(tailPartitionStart->Next, tail); return head ? head : pivot; } } } using namespace NOnStackArgsList; class TQueryArgProcessing::Pipeline { public: Pipeline(TQueryArgProcessing& parent, TUri& subject) : Parent(parent) , Subject(subject) , ArgsCount(0) , IsDirty(false) { } TQueryArg::EProcessed Process() { const TStringBuf& query = Subject.GetField(NUri::TField::FieldQuery); if (query.empty()) return ProcessEmpty(); const char* start = query.data(); return Parse(start, start + query.length(), 0); } TQueryArg::EProcessed ProcessEmpty() { if (Parent.Flags & TQueryArg::FeatureRemoveEmptyQuery) Subject.FldClr(NUri::TField::FieldQuery); return TQueryArg::ProcessedOK; } TQueryArg::EProcessed Parse(const char* str, const char* end, TQArgNode* prev) { str = SkipDelimiter(str, end); if (str == end) { TQArgNode* head = GetHead(prev); TQArgNode* last = GetLast(prev); return FinalizeParsing(head, last); } else { TQArgNode current = MakeArg(prev); const char* next = ExtractArgData(str, end, ¤t); if (!next) return TQueryArg::ProcessedMalformed; TQArgNode* tail = ApplyFilter(prev, ¤t); if (++ArgsCount > MaxCount) return TQueryArg::ProcessedTooMany; return Parse(next, end, tail); } } TQArgNode* ApplyFilter(TQArgNode* prev, TQArgNode* current) { if (Parent.Flags & TQueryArg::FeatureFilter) { TQueryArg arg = {current->Name, current->Value}; if (!Parent.Filter(arg, Parent.FilterData)) { IsDirty = true; return prev; } } if (prev) prev->Next = current; return current; } TQueryArg::EProcessed FinalizeParsing(TQArgNode* head, TQArgNode* last) { if (Parent.Flags & TQueryArg::FeatureSortByName) { head = QSortByName(head, last); IsDirty = true; } if (!IsDirty) return TQueryArg::ProcessedOK; bool dirty = Render(head); bool rewrite = Parent.Flags & TQueryArg::FeatureRewriteDirty; if (dirty && rewrite) Subject.Rewrite(); return (!dirty || rewrite) ? TQueryArg::ProcessedOK : TQueryArg::ProcessedDirty; } bool Render(TQArgNode* head) { std::string& result = Parent.Buffer; result.clear(); result.reserve(Subject.GetField(NUri::TField::FieldQuery).length()); bool first = true; while (head) { if (first) first = false; else result.append("&"); result.append(head->All); head = head->Next; } if (result.empty()) return RenderEmpty(); else return Subject.FldMemSet(NUri::TField::FieldQuery, result); } bool RenderEmpty() { if (Parent.Flags & TQueryArg::FeatureRemoveEmptyQuery) Subject.FldClr(NUri::TField::FieldQuery); return false; } private: TQueryArgProcessing& Parent; TUri& Subject; unsigned ArgsCount; bool IsDirty; static const unsigned MaxCount = 100; }; TQueryArgProcessing::TQueryArgProcessing(ui32 flags, TQueryArgFilter filter, void* filterData) : Flags(flags) , Filter(filter) , FilterData(filterData) { } TQueryArg::EProcessed TQueryArgProcessing::Process(TUri& uri) { Pipeline pipeline(*this, uri); return pipeline.Process(); } }