2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../core/juce_StandardHeader.h"
31 #include "../streams/juce_InputStream.h"
32 #include "../../maths/juce_Random.h"
33 #include "../../core/juce_PlatformUtilities.h"
34 #include "../../text/juce_XmlDocument.h"
35 #include "../../io/streams/juce_MemoryOutputStream.h"
38 //==============================================================================
43 URL::URL (const String
& url_
)
46 int i
= url
.indexOfChar ('?');
52 const int nextAmp
= url
.indexOfChar (i
+ 1, '&');
53 const int equalsPos
= url
.indexOfChar (i
+ 1, '=');
55 if (equalsPos
> i
+ 1)
59 parameters
.set (removeEscapeChars (url
.substring (i
+ 1, equalsPos
)),
60 removeEscapeChars (url
.substring (equalsPos
+ 1)));
62 else if (nextAmp
> 0 && equalsPos
< nextAmp
)
64 parameters
.set (removeEscapeChars (url
.substring (i
+ 1, equalsPos
)),
65 removeEscapeChars (url
.substring (equalsPos
+ 1, nextAmp
)));
73 url
= url
.upToFirstOccurrenceOf ("?", false, false);
77 URL::URL (const URL
& other
)
79 postData (other
.postData
),
80 parameters (other
.parameters
),
81 filesToUpload (other
.filesToUpload
),
82 mimeTypes (other
.mimeTypes
)
86 URL
& URL::operator= (const URL
& other
)
89 postData
= other
.postData
;
90 parameters
= other
.parameters
;
91 filesToUpload
= other
.filesToUpload
;
92 mimeTypes
= other
.mimeTypes
;
103 String
getMangledParameters (const StringPairArray
& parameters
)
107 for (int i
= 0; i
< parameters
.size(); ++i
)
112 p
<< URL::addEscapeChars (parameters
.getAllKeys() [i
], true)
114 << URL::addEscapeChars (parameters
.getAllValues() [i
], true);
120 int findStartOfDomain (const String
& url
)
124 while (CharacterFunctions::isLetterOrDigit (url
[i
])
125 || url
[i
] == '+' || url
[i
] == '-' || url
[i
] == '.')
128 return url
[i
] == ':' ? i
+ 1 : 0;
131 void createHeadersAndPostData (const URL
& url
, String
& headers
, MemoryBlock
& postData
)
133 MemoryOutputStream
data (postData
, false);
135 if (url
.getFilesToUpload().size() > 0)
137 // need to upload some files, so do it as multi-part...
138 const String
boundary (String::toHexString (Random::getSystemRandom().nextInt64()));
140 headers
<< "Content-Type: multipart/form-data; boundary=" << boundary
<< "\r\n";
142 data
<< "--" << boundary
;
145 for (i
= 0; i
< url
.getParameters().size(); ++i
)
147 data
<< "\r\nContent-Disposition: form-data; name=\""
148 << url
.getParameters().getAllKeys() [i
]
150 << url
.getParameters().getAllValues() [i
]
155 for (i
= 0; i
< url
.getFilesToUpload().size(); ++i
)
157 const File
file (url
.getFilesToUpload().getAllValues() [i
]);
158 const String
paramName (url
.getFilesToUpload().getAllKeys() [i
]);
160 data
<< "\r\nContent-Disposition: form-data; name=\"" << paramName
161 << "\"; filename=\"" << file
.getFileName() << "\"\r\n";
163 const String
mimeType (url
.getMimeTypesOfUploadFiles()
164 .getValue (paramName
, String::empty
));
166 if (mimeType
.isNotEmpty())
167 data
<< "Content-Type: " << mimeType
<< "\r\n";
169 data
<< "Content-Transfer-Encoding: binary\r\n\r\n"
170 << file
<< "\r\n--" << boundary
;
178 data
<< getMangledParameters (url
.getParameters()) << url
.getPostData();
181 // just a short text attachment, so use simple url encoding..
182 headers
<< "Content-Type: application/x-www-form-urlencoded\r\nContent-length: "
183 << (int) postData
.getSize() << "\r\n";
188 String
URL::toString (const bool includeGetParameters
) const
190 if (includeGetParameters
&& parameters
.size() > 0)
191 return url
+ "?" + URLHelpers::getMangledParameters (parameters
);
196 bool URL::isWellFormed() const
199 return url
.isNotEmpty();
202 String
URL::getDomain() const
204 int start
= URLHelpers::findStartOfDomain (url
);
205 while (url
[start
] == '/')
208 const int end1
= url
.indexOfChar (start
, '/');
209 const int end2
= url
.indexOfChar (start
, ':');
211 const int end
= (end1
< 0 || end2
< 0) ? jmax (end1
, end2
)
214 return url
.substring (start
, end
);
217 String
URL::getSubPath() const
219 int start
= URLHelpers::findStartOfDomain (url
);
220 while (url
[start
] == '/')
223 const int startOfPath
= url
.indexOfChar (start
, '/') + 1;
225 return startOfPath
<= 0 ? String::empty
226 : url
.substring (startOfPath
);
229 String
URL::getScheme() const
231 return url
.substring (0, URLHelpers::findStartOfDomain (url
) - 1);
234 const URL
URL::withNewSubPath (const String
& newPath
) const
236 int start
= URLHelpers::findStartOfDomain (url
);
237 while (url
[start
] == '/')
240 const int startOfPath
= url
.indexOfChar (start
, '/') + 1;
245 u
.url
= url
.substring (0, startOfPath
);
247 if (! u
.url
.endsWithChar ('/'))
250 if (newPath
.startsWithChar ('/'))
251 u
.url
<< newPath
.substring (1);
258 //==============================================================================
259 bool URL::isProbablyAWebsiteURL (const String
& possibleURL
)
261 const char* validProtocols
[] = { "http:", "ftp:", "https:" };
263 for (int i
= 0; i
< numElementsInArray (validProtocols
); ++i
)
264 if (possibleURL
.startsWithIgnoreCase (validProtocols
[i
]))
267 if (possibleURL
.containsChar ('@')
268 || possibleURL
.containsChar (' '))
271 const String
topLevelDomain (possibleURL
.upToFirstOccurrenceOf ("/", false, false)
272 .fromLastOccurrenceOf (".", false, false));
274 return topLevelDomain
.isNotEmpty() && topLevelDomain
.length() <= 3;
277 bool URL::isProbablyAnEmailAddress (const String
& possibleEmailAddress
)
279 const int atSign
= possibleEmailAddress
.indexOfChar ('@');
282 && possibleEmailAddress
.lastIndexOfChar ('.') > (atSign
+ 1)
283 && (! possibleEmailAddress
.endsWithChar ('.'));
286 //==============================================================================
287 InputStream
* URL::createInputStream (const bool usePostCommand
,
288 OpenStreamProgressCallback
* const progressCallback
,
289 void* const progressCallbackContext
,
290 const String
& extraHeaders
,
292 StringPairArray
* const responseHeaders
) const
295 MemoryBlock headersAndPostData
;
298 URLHelpers::createHeadersAndPostData (*this, headers
, headersAndPostData
);
300 headers
+= extraHeaders
;
302 if (! headers
.endsWithChar ('\n'))
305 return createNativeStream (toString (! usePostCommand
), usePostCommand
, headersAndPostData
,
306 progressCallback
, progressCallbackContext
,
307 headers
, timeOutMs
, responseHeaders
);
310 //==============================================================================
311 bool URL::readEntireBinaryStream (MemoryBlock
& destData
,
312 const bool usePostCommand
) const
314 const ScopedPointer
<InputStream
> in (createInputStream (usePostCommand
));
318 in
->readIntoMemoryBlock (destData
);
325 String
URL::readEntireTextStream (const bool usePostCommand
) const
327 const ScopedPointer
<InputStream
> in (createInputStream (usePostCommand
));
330 return in
->readEntireStreamAsString();
332 return String::empty
;
335 XmlElement
* URL::readEntireXmlStream (const bool usePostCommand
) const
337 return XmlDocument::parse (readEntireTextStream (usePostCommand
));
340 //==============================================================================
341 const URL
URL::withParameter (const String
& parameterName
,
342 const String
& parameterValue
) const
345 u
.parameters
.set (parameterName
, parameterValue
);
349 const URL
URL::withFileToUpload (const String
& parameterName
,
350 const File
& fileToUpload
,
351 const String
& mimeType
) const
353 jassert (mimeType
.isNotEmpty()); // You need to supply a mime type!
356 u
.filesToUpload
.set (parameterName
, fileToUpload
.getFullPathName());
357 u
.mimeTypes
.set (parameterName
, mimeType
);
361 const URL
URL::withPOSTData (const String
& postData_
) const
364 u
.postData
= postData_
;
368 const StringPairArray
& URL::getParameters() const
373 const StringPairArray
& URL::getFilesToUpload() const
375 return filesToUpload
;
378 const StringPairArray
& URL::getMimeTypesOfUploadFiles() const
383 //==============================================================================
384 String
URL::removeEscapeChars (const String
& s
)
386 String
result (s
.replaceCharacter ('+', ' '));
388 if (! result
.containsChar ('%'))
391 // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode
392 // after all the replacements have been made, so that multi-byte chars are handled.
393 Array
<char> utf8 (result
.toUTF8().getAddress(), result
.getNumBytesAsUTF8());
395 for (int i
= 0; i
< utf8
.size(); ++i
)
397 if (utf8
.getUnchecked(i
) == '%')
399 const int hexDigit1
= CharacterFunctions::getHexDigitValue (utf8
[i
+ 1]);
400 const int hexDigit2
= CharacterFunctions::getHexDigitValue (utf8
[i
+ 2]);
402 if (hexDigit1
>= 0 && hexDigit2
>= 0)
404 utf8
.set (i
, (char) ((hexDigit1
<< 4) + hexDigit2
));
405 utf8
.removeRange (i
+ 1, 2);
410 return String::fromUTF8 (utf8
.getRawDataPointer(), utf8
.size());
413 String
URL::addEscapeChars (const String
& s
, const bool isParameter
)
415 const CharPointer_UTF8
legalChars (isParameter
? "_-.*!'()"
418 Array
<char> utf8 (s
.toUTF8().getAddress(), s
.getNumBytesAsUTF8());
420 for (int i
= 0; i
< utf8
.size(); ++i
)
422 const char c
= utf8
.getUnchecked(i
);
424 if (! (CharacterFunctions::isLetterOrDigit (c
)
425 || legalChars
.indexOf ((juce_wchar
) c
) >= 0))
433 static const char* const hexDigits
= "0123456789abcdef";
436 utf8
.insert (++i
, hexDigits
[((uint8
) c
) >> 4]);
437 utf8
.insert (++i
, hexDigits
[c
& 15]);
442 return String::fromUTF8 (utf8
.getRawDataPointer(), utf8
.size());
445 //==============================================================================
446 bool URL::launchInDefaultBrowser() const
448 String
u (toString (true));
450 if (u
.containsChar ('@') && ! u
.containsChar (':'))
453 return PlatformUtilities::openDocument (u
, String::empty
);