001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.quercus.lib;
031:
032: import com.caucho.quercus.annotation.Optional;
033: import com.caucho.quercus.env.*;
034: import com.caucho.quercus.lib.file.BinaryInput;
035: import com.caucho.quercus.lib.file.BinaryStream;
036: import com.caucho.quercus.lib.file.FileModule;
037: import com.caucho.quercus.module.AbstractQuercusModule;
038: import com.caucho.util.Base64;
039: import com.caucho.util.CharBuffer;
040: import com.caucho.util.L10N;
041: import com.caucho.vfs.TempBuffer;
042:
043: import java.io.IOException;
044: import java.io.InputStream;
045: import java.io.InputStreamReader;
046: import java.io.LineNumberReader;
047: import java.io.OutputStream;
048: import java.io.OutputStreamWriter;
049: import java.net.Socket;
050: import java.net.URL;
051: import java.util.LinkedHashMap;
052: import java.util.Map;
053: import java.util.Set;
054: import java.util.logging.Logger;
055:
056: /**
057: * PHP URL
058: */
059: public class UrlModule extends AbstractQuercusModule {
060: private static final L10N L = new L10N(UrlModule.class);
061: private static final Logger log = Logger.getLogger(UrlModule.class
062: .getName());
063:
064: /**
065: * Encodes base64
066: */
067: public static String base64_encode(InputStream is) {
068: CharBuffer cb = new CharBuffer();
069:
070: TempBuffer tb = TempBuffer.allocate();
071: byte[] buffer = tb.getBuffer();
072:
073: int len;
074: int offset = 0;
075:
076: try {
077: while ((len = is.read(buffer, offset, buffer.length
078: - offset)) >= 0) {
079: int tail = len % 3;
080:
081: Base64.encode(cb, buffer, 0, len - tail);
082:
083: System.arraycopy(buffer, len - tail, buffer, 0, tail);
084: offset = tail;
085: }
086:
087: if (offset > 0)
088: Base64.encode(cb, buffer, 0, offset);
089: } catch (IOException e) {
090: throw new RuntimeException(e);
091: } finally {
092: TempBuffer.free(tb);
093: }
094:
095: return cb.toString();
096: }
097:
098: /**
099: * Decodes base64
100: */
101: public static String base64_decode(String str) {
102: if (str == null)
103: return "";
104:
105: return Base64.decode(str);
106: }
107:
108: /**
109: * Connects to the given URL using a HEAD request to retreive
110: * the headers sent in the response.
111: */
112: public static Value get_headers(Env env, String urlString,
113: @Optional
114: Value format) {
115: Socket socket = null;
116:
117: try {
118: URL url = new URL(urlString);
119:
120: if (!url.getProtocol().equals("http")
121: && !url.getProtocol().equals("https")) {
122: env.warning(L.l("Not an HTTP URL"));
123: return null;
124: }
125:
126: int port = 80;
127:
128: if (url.getPort() < 0) {
129: if (url.getProtocol().equals("http"))
130: port = 80;
131: else if (url.getProtocol().equals("https"))
132: port = 443;
133: } else {
134: port = url.getPort();
135: }
136:
137: socket = new Socket(url.getHost(), port);
138:
139: OutputStream out = socket.getOutputStream();
140: InputStream in = socket.getInputStream();
141:
142: StringBuilder request = new StringBuilder();
143:
144: request.append("HEAD ");
145:
146: if (url.getPath() != null)
147: request.append(url.getPath());
148:
149: if (url.getQuery() != null)
150: request.append("?" + url.getQuery());
151:
152: if (url.getRef() != null)
153: request.append("#" + url.getRef());
154:
155: request.append(" HTTP/1.0\r\n");
156:
157: if (url.getHost() != null)
158: request.append("Host: " + url.getHost() + "\r\n");
159:
160: request.append("\r\n");
161:
162: OutputStreamWriter writer = new OutputStreamWriter(out);
163: writer.write(request.toString());
164: writer.flush();
165:
166: LineNumberReader reader = new LineNumberReader(
167: new InputStreamReader(in));
168:
169: ArrayValue result = new ArrayValueImpl();
170:
171: if (format.toBoolean()) {
172: for (String line = reader.readLine(); line != null; line = reader
173: .readLine()) {
174: line = line.trim();
175:
176: if (line.length() == 0)
177: continue;
178:
179: int colon = line.indexOf(':');
180:
181: ArrayValue values;
182:
183: if (colon < 0)
184: result.put(env.createString(line.trim()));
185: else {
186: StringValue key = env.createString(line
187: .substring(0, colon).trim());
188:
189: StringValue value;
190:
191: if (colon < line.length())
192: value = env.createString(line.substring(
193: colon + 1).trim());
194: else
195: value = env.createEmptyString();
196:
197: if (result.get(key) != UnsetValue.UNSET)
198: values = (ArrayValue) result.get(key);
199: else {
200: values = new ArrayValueImpl();
201:
202: result.put(key, values);
203: }
204:
205: values.put(value);
206: }
207: }
208:
209: // collapse single entries
210: for (Value key : result.keySet()) {
211: Value value = result.get(key);
212:
213: if (value.isArray()
214: && ((ArrayValue) value).getSize() == 1)
215: result.put(key, ((ArrayValue) value)
216: .get(LongValue.ZERO));
217: }
218: } else {
219: for (String line = reader.readLine(); line != null; line = reader
220: .readLine()) {
221: line = line.trim();
222:
223: if (line.length() == 0)
224: continue;
225:
226: result.put(env.createString(line.trim()));
227: }
228: }
229:
230: return result;
231: } catch (Exception e) {
232: env.warning(e);
233:
234: return BooleanValue.FALSE;
235: } finally {
236: try {
237: if (socket != null)
238: socket.close();
239: } catch (IOException e) {
240: env.warning(e);
241: }
242: }
243: }
244:
245: /**
246: * Extracts the meta tags from a file and returns them as an array.
247: */
248: public static Value get_meta_tags(Env env, StringValue filename,
249: @Optional("false")
250: boolean use_include_path) {
251: InputStream in = null;
252:
253: ArrayValue result = new ArrayValueImpl();
254:
255: try {
256: BinaryStream stream = FileModule.fopen(env, filename, "r",
257: use_include_path, null);
258:
259: if (stream == null || !(stream instanceof BinaryInput))
260: return result;
261:
262: BinaryInput input = (BinaryInput) stream;
263:
264: while (!input.isEOF()) {
265: String tag = getNextTag(input);
266:
267: if (tag.equalsIgnoreCase("meta")) {
268: String name = null;
269: String content = null;
270:
271: String[] attr;
272:
273: while ((attr = getNextAttribute(input)) != null) {
274: if (name == null
275: && attr[0].equalsIgnoreCase("name")) {
276: if (attr.length > 1)
277: name = attr[1];
278: } else if (content == null
279: && attr[0].equalsIgnoreCase("content")) {
280: if (attr.length > 1)
281: content = attr[1];
282: }
283:
284: if (name != null && content != null) {
285: result.put(env.createString(name), env
286: .createString(content));
287: break;
288: }
289: }
290: } else if (tag.equalsIgnoreCase("/head"))
291: break;
292: }
293: } catch (IOException e) {
294: env.warning(e);
295: } finally {
296: try {
297: if (in != null)
298: in.close();
299: } catch (IOException e) {
300: env.warning(e);
301: }
302: }
303:
304: return result;
305: }
306:
307: public static Value http_build_query(Env env, Value formdata,
308: @Optional
309: StringValue numeric_prefix, @Optional("'&'")
310: StringValue separator) {
311: StringValue result = env.createUnicodeBuilder();
312:
313: httpBuildQueryImpl(env, result, formdata, env
314: .createEmptyString(), numeric_prefix, separator);
315:
316: return result;
317: }
318:
319: private static void httpBuildQueryImpl(Env env, StringValue result,
320: Value formdata, StringValue path,
321: StringValue numeric_prefix, StringValue separator) {
322: Set<Map.Entry<Value, Value>> entrySet;
323:
324: if (formdata.isArray())
325: entrySet = ((ArrayValue) formdata).entrySet();
326: else if (formdata.isObject()) {
327: Set<? extends Map.Entry<Value, Value>> stringEntrySet = ((ObjectValue) formdata)
328: .entrySet();
329:
330: LinkedHashMap<Value, Value> valueMap = new LinkedHashMap<Value, Value>();
331:
332: for (Map.Entry<Value, Value> entry : stringEntrySet)
333: valueMap.put(entry.getKey(), entry.getValue());
334:
335: entrySet = valueMap.entrySet();
336: } else {
337: env.warning(L.l("formdata must be an array or object"));
338:
339: return;
340: }
341:
342: boolean isFirst = true;
343: for (Map.Entry<Value, Value> entry : entrySet) {
344: if (!isFirst) {
345: if (separator != null)
346: result.append(separator);
347: else
348: result.append("&");
349: }
350: isFirst = false;
351:
352: StringValue newPath = makeNewPath(path, entry.getKey(),
353: numeric_prefix);
354: Value entryValue = entry.getValue();
355:
356: if (entryValue.isArray() || entryValue.isObject()) {
357: // can always throw away the numeric prefix on recursive calls
358: httpBuildQueryImpl(env, result, entryValue, newPath,
359: null, separator);
360:
361: } else {
362: result.append(newPath);
363: result.append("=");
364: result.append(urlencode(entry.getValue()
365: .toStringValue()));
366: }
367: }
368: }
369:
370: private static StringValue makeNewPath(StringValue oldPath,
371: Value key, StringValue numeric_prefix) {
372: StringValue path = oldPath.createStringBuilder();
373:
374: if (oldPath.length() != 0) {
375: path.append(oldPath);
376: //path.append('[');
377: path.append("%5B");
378: urlencode(path, key.toStringValue());
379: //path.append(']');
380: path.append("%5D");
381:
382: return path;
383: } else if (key.isLongConvertible() && numeric_prefix != null) {
384: urlencode(path, numeric_prefix);
385: urlencode(path, key.toStringValue());
386:
387: return path;
388: } else {
389: urlencode(path, key.toStringValue());
390:
391: return path;
392: }
393: }
394:
395: /**
396: * Creates a http string.
397: */
398: /*
399: public String http_build_query(Value value,
400: @Optional String prefix)
401: {
402: StringBuilder sb = new StringBuilder();
403:
404: int index = 0;
405: if (value instanceof ArrayValue) {
406: ArrayValue array = (ArrayValue) value;
407:
408: for (Map.Entry<Value,Value> entry : array.entrySet()) {
409: Value keyValue = entry.getKey();
410: Value v = entry.getValue();
411:
412: String key;
413:
414: if (keyValue.isLongConvertible())
415: key = prefix + keyValue;
416: else
417: key = keyValue.toString();
418:
419: if (v instanceof ArrayValue)
420: http_build_query(sb, key, (ArrayValue) v);
421: else {
422: if (sb.length() > 0)
423: sb.append('&');
424:
425: sb.append(key);
426: sb.append('=');
427: urlencode(sb, v.toString());
428: }
429: }
430: }
431:
432: return sb.toString();
433: }
434: */
435:
436: /**
437: * Creates a http string.
438: */
439: /*
440: private void http_build_query(StringBuilder sb,
441: String prefix,
442: ArrayValue array)
443: {
444: for (Map.Entry<Value,Value> entry : array.entrySet()) {
445: Value keyValue = entry.getKey();
446: Value v = entry.getValue();
447:
448: String key = prefix + '[' + keyValue + ']';
449:
450: if (v instanceof ArrayValue)
451: http_build_query(sb, key, (ArrayValue) v);
452: else {
453: if (sb.length() > 0)
454: sb.append('&');
455:
456: sb.append(key);
457: sb.append('=');
458: urlencode(sb, v.toString());
459: }
460: }
461: }
462: */
463:
464: /**
465: * Parses the URL into an array.
466: */
467: public static Value parse_url(Env env, StringValue str) {
468: if (str == null)
469: str = env.createEmptyString();
470:
471: int i = 0;
472: int length = str.length();
473:
474: StringValue sb = env.createUnicodeBuilder();
475:
476: ArrayValueImpl value = new ArrayValueImpl();
477:
478: // XXX: php/1i04.qa contradicts:
479: // value.put("path", "");
480:
481: ParseUrlState state = ParseUrlState.INIT;
482:
483: StringValue user = null;
484:
485: for (; i < length; i++) {
486: char ch = str.charAt(i);
487:
488: switch (ch) {
489: case ':':
490: if (state == ParseUrlState.INIT) {
491: value.put(env.createString("scheme"), sb);
492: sb = env.createUnicodeBuilder();
493:
494: if (length <= i + 1 || str.charAt(i + 1) != '/') {
495: state = ParseUrlState.PATH;
496: } else if (length <= i + 2
497: || str.charAt(i + 2) != '/') {
498: state = ParseUrlState.PATH;
499: } else if (length <= i + 3
500: || str.charAt(i + 3) != '/') {
501: i += 2;
502: state = ParseUrlState.USER;
503: } else {
504: // file:///foo
505:
506: i += 2;
507: state = ParseUrlState.PATH;
508: }
509: } else if (state == ParseUrlState.USER) {
510: user = sb;
511: sb = env.createUnicodeBuilder();
512: state = ParseUrlState.PASS;
513: } else if (state == ParseUrlState.HOST) {
514: value.put(env.createString("host"), sb);
515: sb = env.createUnicodeBuilder();
516: state = ParseUrlState.PORT;
517: } else
518: sb.append(ch);
519: break;
520:
521: case '@':
522: if (state == ParseUrlState.USER) {
523: value.put(env.createString("user"), sb);
524: sb = env.createUnicodeBuilder();
525: state = ParseUrlState.HOST;
526: } else if (state == ParseUrlState.PASS) {
527: value.put(env.createString("user"), user);
528: value.put(env.createString("pass"), sb);
529: sb = env.createUnicodeBuilder();
530: state = ParseUrlState.HOST;
531: } else
532: sb.append(ch);
533: break;
534:
535: case '/':
536: if (state == ParseUrlState.USER
537: || state == ParseUrlState.HOST) {
538: value.put(env.createString("host"), sb);
539: sb = env.createUnicodeBuilder();
540: state = ParseUrlState.PATH;
541: sb.append(ch);
542: } else if (state == ParseUrlState.PASS) {
543: value.put(env.createString("host"), user);
544: value.put(env.createString("port"), new LongValue(
545: sb.toLong()));
546: sb = env.createUnicodeBuilder();
547: state = ParseUrlState.PATH;
548: sb.append(ch);
549: } else if (state == ParseUrlState.PORT) {
550: value.put(env.createString("port"), new LongValue(
551: sb.toLong()));
552: sb = env.createUnicodeBuilder();
553: state = ParseUrlState.PATH;
554: sb.append(ch);
555: } else
556: sb.append(ch);
557: break;
558:
559: case '?':
560: if (state == ParseUrlState.USER
561: || state == ParseUrlState.HOST) {
562: value.put(env.createString("host"), sb);
563: sb = env.createUnicodeBuilder();
564: state = ParseUrlState.QUERY;
565: } else if (state == ParseUrlState.PASS) {
566: value.put(env.createString("host"), user);
567: value.put(env.createString("port"), new LongValue(
568: sb.toLong()));
569: sb = env.createUnicodeBuilder();
570: state = ParseUrlState.QUERY;
571: } else if (state == ParseUrlState.PORT) {
572: value.put(env.createString("port"), new LongValue(
573: sb.toLong()));
574: sb = env.createUnicodeBuilder();
575: state = ParseUrlState.QUERY;
576: } else if (state == ParseUrlState.PATH) {
577: if (sb.length() > 0)
578: value.put(env.createString("path"), sb);
579: sb = env.createUnicodeBuilder();
580: state = ParseUrlState.QUERY;
581: } else
582: sb.append(ch);
583: break;
584:
585: case '#':
586: if (state == ParseUrlState.USER
587: || state == ParseUrlState.HOST) {
588: value.put(env.createString("host"), sb);
589: sb = env.createUnicodeBuilder();
590: state = ParseUrlState.FRAGMENT;
591: } else if (state == ParseUrlState.PASS) {
592: value.put(env.createString("host"), user);
593: value.put(env.createString("port"), new LongValue(
594: sb.toLong()));
595: sb = env.createUnicodeBuilder();
596: state = ParseUrlState.FRAGMENT;
597: } else if (state == ParseUrlState.PORT) {
598: value.put(env.createString("port"), new LongValue(
599: sb.toLong()));
600: sb = env.createUnicodeBuilder();
601: state = ParseUrlState.FRAGMENT;
602: } else if (state == ParseUrlState.PATH) {
603: if (sb.length() > 0)
604: value.put(env.createString("path"), sb);
605: sb = env.createUnicodeBuilder();
606: state = ParseUrlState.FRAGMENT;
607: } else if (state == ParseUrlState.QUERY) {
608: if (sb.length() > 0)
609: value.put(env.createString("query"), sb);
610: sb = env.createUnicodeBuilder();
611: state = ParseUrlState.FRAGMENT;
612: } else
613: sb.append(ch);
614: break;
615:
616: default:
617: sb.append((char) ch);
618: break;
619: }
620: }
621:
622: if (sb.length() == 0) {
623: } else if (state == ParseUrlState.USER
624: || state == ParseUrlState.HOST)
625: value.put(env.createString("host"), sb);
626: else if (state == ParseUrlState.PASS) {
627: value.put(env.createString("host"), user);
628: value.put(env.createString("port"), new LongValue(sb
629: .toLong()));
630: } else if (state == ParseUrlState.PORT) {
631: value.put(env.createString("port"), new LongValue(sb
632: .toLong()));
633: } else if (state == ParseUrlState.QUERY)
634: value.put(env.createString("query"), sb);
635: else if (state == ParseUrlState.FRAGMENT)
636: value.put(env.createString("fragment"), sb);
637: else
638: value.put(env.createString("path"), sb);
639:
640: return value;
641: }
642:
643: /**
644: * Returns the decoded string.
645: */
646: public static String rawurldecode(String s) {
647: if (s == null)
648: return "";
649:
650: int len = s.length();
651: StringBuilder sb = new StringBuilder();
652:
653: for (int i = 0; i < len; i++) {
654: char ch = s.charAt(i);
655:
656: if (ch == '%' && i + 2 < len) {
657: int d1 = s.charAt(i + 1);
658: int d2 = s.charAt(i + 2);
659:
660: int v = 0;
661:
662: if ('0' <= d1 && d1 <= '9')
663: v = 16 * (d1 - '0');
664: else if ('a' <= d1 && d1 <= 'f')
665: v = 16 * (d1 - 'a' + 10);
666: else if ('A' <= d1 && d1 <= 'F')
667: v = 16 * (d1 - 'A' + 10);
668: else {
669: sb.append('%');
670: continue;
671: }
672:
673: if ('0' <= d2 && d2 <= '9')
674: v += (d2 - '0');
675: else if ('a' <= d2 && d2 <= 'f')
676: v += (d2 - 'a' + 10);
677: else if ('A' <= d2 && d2 <= 'F')
678: v += (d2 - 'A' + 10);
679: else {
680: sb.append('%');
681: continue;
682: }
683:
684: i += 2;
685: sb.append((char) v);
686: } else
687: sb.append(ch);
688: }
689:
690: return sb.toString();
691: }
692:
693: /**
694: * Encodes the url
695: */
696: public static String rawurlencode(String str) {
697: if (str == null)
698: return "";
699:
700: StringBuilder sb = new StringBuilder();
701:
702: for (int i = 0; i < str.length(); i++) {
703: char ch = str.charAt(i);
704:
705: if ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
706: || '0' <= ch && ch <= '9' || ch == '-' || ch == '_'
707: || ch == '.') {
708: sb.append(ch);
709: } else {
710: sb.append('%');
711: sb.append(toHexDigit(ch >> 4));
712: sb.append(toHexDigit(ch));
713: }
714: }
715:
716: return sb.toString();
717: }
718:
719: enum ParseUrlState {
720: INIT, USER, PASS, HOST, PORT, PATH, QUERY, FRAGMENT
721: };
722:
723: /**
724: * Gets the magic quotes value.
725: */
726: public static StringValue urlencode(StringValue str) {
727: StringValue sb = str.createStringBuilder();
728:
729: urlencode(sb, str);
730:
731: return sb;
732: }
733:
734: /**
735: * Gets the magic quotes value.
736: */
737: private static void urlencode(StringValue sb, StringValue str) {
738: int len = str.length();
739:
740: for (int i = 0; i < len; i++) {
741: char ch = str.charAt(i);
742:
743: if ('a' <= ch && ch <= 'z')
744: sb.append(ch);
745: else if ('A' <= ch && ch <= 'Z')
746: sb.append(ch);
747: else if ('0' <= ch && ch <= '9')
748: sb.append(ch);
749: else if (ch == '-' || ch == '_' || ch == '.')
750: sb.append(ch);
751: else if (ch == ' ')
752: sb.append('+');
753: else {
754: sb.append('%');
755: sb.append(toHexDigit(ch / 16));
756: sb.append(toHexDigit(ch));
757: }
758: }
759: }
760:
761: /**
762: * Returns the decoded string.
763: */
764: public static String urldecode(String s) {
765: if (s == null)
766: return "";
767:
768: int len = s.length();
769: StringBuilder sb = new StringBuilder();
770:
771: for (int i = 0; i < len; i++) {
772: char ch = s.charAt(i);
773:
774: if (ch == '%' && i + 2 < len) {
775: int d1 = s.charAt(i + 1);
776: int d2 = s.charAt(i + 2);
777:
778: int v = 0;
779:
780: if ('0' <= d1 && d1 <= '9')
781: v = 16 * (d1 - '0');
782: else if ('a' <= d1 && d1 <= 'f')
783: v = 16 * (d1 - 'a' + 10);
784: else if ('A' <= d1 && d1 <= 'F')
785: v = 16 * (d1 - 'A' + 10);
786: else {
787: sb.append('%');
788: continue;
789: }
790:
791: if ('0' <= d2 && d2 <= '9')
792: v += (d2 - '0');
793: else if ('a' <= d2 && d2 <= 'f')
794: v += (d2 - 'a' + 10);
795: else if ('A' <= d2 && d2 <= 'F')
796: v += (d2 - 'A' + 10);
797: else {
798: sb.append('%');
799: continue;
800: }
801:
802: i += 2;
803: sb.append((char) v);
804: } else if (ch == '+')
805: sb.append(' ');
806: else
807: sb.append(ch);
808: }
809:
810: return sb.toString();
811: }
812:
813: private static String getNextTag(BinaryInput input)
814: throws IOException {
815: StringBuilder tag = new StringBuilder();
816:
817: for (int ch = 0; !input.isEOF() && ch != '<'; ch = input.read()) {
818: }
819:
820: while (!input.isEOF()) {
821: int ch = input.read();
822:
823: if (Character.isWhitespace(ch))
824: break;
825:
826: tag.append((char) ch);
827: }
828:
829: return tag.toString();
830: }
831:
832: /**
833: * Finds the next attribute in the stream and return the key and value
834: * as an array.
835: */
836: private static String[] getNextAttribute(BinaryInput input)
837: throws IOException {
838: int ch;
839:
840: consumeWhiteSpace(input);
841:
842: StringBuilder attribute = new StringBuilder();
843:
844: while (!input.isEOF()) {
845: ch = input.read();
846:
847: if (isValidAttributeCharacter(ch))
848: attribute.append((char) ch);
849: else {
850: input.unread();
851: break;
852: }
853: }
854:
855: if (attribute.length() == 0)
856: return null;
857:
858: consumeWhiteSpace(input);
859:
860: if (input.isEOF())
861: return new String[] { attribute.toString() };
862:
863: ch = input.read();
864: if (ch != '=') {
865: input.unread();
866:
867: return new String[] { attribute.toString() };
868: }
869:
870: consumeWhiteSpace(input);
871:
872: // check for quoting
873: int quote = ' ';
874: boolean quoted = false;
875:
876: if (input.isEOF())
877: return new String[] { attribute.toString() };
878:
879: ch = input.read();
880:
881: if (ch == '"' || ch == '\'') {
882: quoted = true;
883: quote = ch;
884: } else
885: input.unread();
886:
887: StringBuilder value = new StringBuilder();
888:
889: while (!input.isEOF()) {
890: ch = input.read();
891:
892: // mimics PHP behavior
893: if ((quoted && ch == quote)
894: || (!quoted && Character.isWhitespace(ch))
895: || ch == '>')
896: break;
897:
898: value.append((char) ch);
899: }
900:
901: return new String[] { attribute.toString(), value.toString() };
902: }
903:
904: private static void consumeWhiteSpace(BinaryInput input)
905: throws IOException {
906: int ch = 0;
907:
908: while (!input.isEOF()
909: && Character.isWhitespace(ch = input.read())) {
910: }
911:
912: if (!Character.isWhitespace(ch))
913: input.unread();
914: }
915:
916: private static boolean isValidAttributeCharacter(int ch) {
917: return Character.isLetterOrDigit(ch) || (ch == '-')
918: || (ch == '.') || (ch == '_') || (ch == ':');
919: }
920:
921: private static char toHexDigit(int d) {
922: d = d & 0xf;
923:
924: if (d < 10)
925: return (char) ('0' + d);
926: else
927: return (char) ('A' + d - 10);
928: }
929: }
|